Objective-C 中的 Block (1)

block 是 Objective-C 中的重要技术。本文主要是写了一点对 block 应用的探究。

什么是 Block

首先补充几个概念和解释帮助理解,大神们直接略过:

  1. block 本质是一个结构体。
  2. 结构体可以创建在栈上,堆上,还可以在全局区,因此就有对应的三种 block:栈上的,堆上的和全局的。

如果 block 中没有捕捉变量,就是全局类型的, block 以及运行结果在编译期就能决定了,因此放在全局/静态区。

如果 block 捕捉了变量,就必须依赖于其创建时的上下文,并被储存在栈内,栈弹出之后就会失效(失效并不代表着执行 block 一定会失败)。

如果希望 block 的存活不再依赖于栈,就必须通过 copy 方法将 block 转化成堆类型的 block。堆类型的 block 保存在堆中,并带有引用计数,block 的引用计数和 Objective-C 对象的引用计数采用类似的原理,但并不是同一套实现,二者是有所区别的。

Block 怎样捕获变量

block 捕捉变量是通过复制,捕捉值类型的变量是复制值,捕捉引用类型的变量是通过复制指针,捕捉到的变量和指针在 block 内部的修改无法影响到 block 外部的值。可以在声明变量的时候加上 __block 标记。带有 __block 标记的变量在被捕捉后就可以被修改了。

问题的产生

Objective-C 是如何做到这一特性的呢?如果一个变量 foo 带有 __block 标记,并且被一个 block 捕捉,这时候在 block 内部修改 foo 的值会引起 block 外变量值的变化,不妨假设 block 创建了一个引用类型的变量指向了栈中的变量 foo 。不过想想都不可能,很明显这是不可靠地,因为程序运行过程中栈在不断变化,一旦 block 失去了当时栈的上下文,必然会产生野指针。

对推测的验证

所以我写了个 Demo ,用 block 捕获变量,来观察 block 是怎样操作的。

XCode Version 8.2.1 (8C1002) ARC Objective-C

#import "ViewController.h"typedef double (^SampleMultiplyBlockRef)();@interface ViewController ()@property (nonatomic, copy) SampleMultiplyBlockRef block;@end@implementation ViewController- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
  __block double a = 10;
  __block double b = 20;
  // 变量初始化
  NSLog(@"Var a after init: %f, %p", a, &a);
  NSLog(@"Var b after init: %f, %p", b, &b);
  a = 25;
  a = 22;
  // 修改变量值
  NSLog(@"Var a after change: %f, %p", a, &a);
  NSLog(@"Var b after change: %f, %p", b, &b);
  SampleMultiplyBlockRef multiply = ^(){
    // block 中修改变量值前
    NSLog(@"Var a in block: %f, %p", a, &a);
    NSLog(@"Var b in block: %f, %p", b, &b);
    a = a + 1;
    b = b + 1;
    // block 中修改变量值后
    NSLog(@"Var a after change: %f, %p", a, &a);
    NSLog(@"Var b after change: %f, %p", b, &b);
    return a * b;
  };
  // 变量被捕捉后,block执行前
  NSLog(@"Var a out of block before execute: %f, %p", a, &a);
  NSLog(@"Var b out of block before execute: %f, %p", b, &b);
  // block 的地址
  NSLog(@"mulitply %p", &multiply);
  // block 的返回
  NSLog(@"Where is %f", multiply());
  // block 执行后
  NSLog(@"Var a out of block: %f, %p", a, &a);
  NSLog(@"Var b out of block: %f, %p", b, &b);
  // 储存到属性中(注意:类型是 copy)
  self.block = multiply;
  NSLog(@"mulitply %p", &multiply);}- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  self.block();
  // 作为属性储存的 block 地址
  NSLog(@"mulitply %p", &_block);
}
@end

Demo 比较简单,就是在 viewDidLoad 方法内创建 block 并作为属性储存下来,在 viewDidAppear 方法中执行 block。

运行后我们一条一条的分析输出结果,主要关注值和内存地址:

首先在方法内部创建 a 和 b 两个值类型变量。

2017-01-13 09:24:17.648 Test Project[36246:1304073] Var a after init: 10.000000, 0x7fff5bbf9a482017-01-13 09:24:17.649 Test Project[36246:1304073] Var b after init: 20.000000, 0x7fff5bbf9a28

之后在方法内部修改 a 和 b 的值,可以看到两个变量的值发生了变化,但是内存地址并没有变化(废话)。

2017-01-13 09:24:17.649 Test Project[36246:1304073] Var a after change: 25.000000, 0x7fff5bbf9a482017-01-13 09:24:17.650 Test Project[36246:1304073] Var b after change: 22.000000, 0x7fff5bbf9a28

block 声明完成,此时发现两变量的地址都发生了变化,应该是已经完成变量的捕获。

2017-01-13 09:24:17.650 Test Project[36246:1304073] Var a out of block before execute: 25.000000, 0x60800002e2d82017-01-13 09:24:17.651 Test Project[36246:1304073] Var b out of block before execute: 22.000000, 0x60800002e338

block 在所在栈内执行过程中,对两变量赋值前的情况。

2017-01-13 09:24:17.652 Test Project[36246:1304073] Var a in block: 25.000000, 0x60800002e2d8
2017-01-13 09:24:17.652 Test Project[36246:1304073] Var b in block: 22.000000, 0x60800002e338

block 在所在栈内执行过程中,对两变量赋值后的情况。

2017-01-13 09:24:17.653 Test Project[36246:1304073] Var a after change: 26.000000, 0x60800002e2d8
2017-01-13 09:24:17.653 Test Project[36246:1304073] Var b after change: 23.000000, 0x60800002e338

block 在所在的栈内执行完成后,两变量的情况。

2017-01-13 09:24:17.654 Test Project[36246:1304073] Var a out of block: 26.000000, 0x60800002e2d8
2017-01-13 09:24:17.654 Test Project[36246:1304073] Var b out of block: 23.000000, 0x60800002e338

拷贝到堆中的 block 执行过程中,对两变量赋值前的情况。

2017-01-13 09:24:17.666 Test Project[36246:1304073] Var a in block: 26.000000, 0x60800002e2d8
2017-01-13 09:24:17.666 Test Project[36246:1304073] Var b in block: 23.000000, 0x60800002e338

拷贝到堆中的 block 执行过程中,对两变量赋值后的情况。

2017-01-13 09:24:17.667 Test Project[36246:1304073] Var a after change: 27.000000, 0x60800002e2d8
2017-01-13 09:24:17.667 Test Project[36246:1304073] Var b after change: 24.000000, 0x60800002e338

栈中刚声明后的 block 地址

2017-01-13 09:24:17.651 Test Project[36246:1304073] mulitply 0x7fff5bbf99f8

栈中执行后的 block 地址

2017-01-13 09:24:17.656 Test Project[36246:1304073] mulitply 0x7fff5bbf99f8

拷贝到堆中的 block 地址

2017-01-13 09:24:17.667 Test Project[36246:1304073] mulitply 0x7fac94d06338

结论

这样看下来,在 block 捕获后,变量的地址就发生了变化,变量的存活也不再依赖于栈,因此应该是储存在堆区。
block 复制到堆后,之前捕获的的变量地址依然没有变化,可以推断 block 中的变量值应该是指针指向了同一个地址。

关注微信订阅号:iOS开发笔记本

http://weixin.qq.com/r/Kji_plrEqwfUrR7F9204

关注知乎专栏:iOS开发学记笔记

https://zhuanlan.zhihu.com/iOSDevNote

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 最近在做项目整理,闲暇之余和大家分享一下Block。block是在我们应用程序中应该是最多的地方之一吧,各式各样的...
    coenen阅读 459评论 0 2
  • 1: 什么是block?1.0: Block的语法1.1: block编译转换结构1.2: block实际结构 2...
    iYeso阅读 796评论 0 5
  • 原文地址:Objective-C中的Block 1.相关概念 在这篇笔记开始之前,我们需要对以下概念有所了解。 1...
    默默_David阅读 386评论 0 1
  • .相关概念 在这篇笔记开始之前,我们需要对以下概念有所了解。 1.1 操作系统中的栈和堆 注:这里所说的堆和栈与数...
    狼凤皇阅读 474评论 0 0
  • 目录 Block底层解析什么是block?block编译转换结构block实际结构block的类型NSConcre...
    tripleCC阅读 33,052评论 32 388