Block 的实现
Block 的实质
Block 实质上是 Object-C 对象
所谓截获自动变量值,意味着在执行 Block 语法时,Block 表达式所使用的自动变量值被保存在 Block 的结构体实例中
(有一、、复杂,随缘看吧)
Object-C 中由类生成对象,意味着,像结构体这样“生成由该类生成的对象的结构体实例”,生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量 isa 保持该类的结构体实例指针
截获自动变量
Block 语法表达式中使用的自动变量被作为成员变量被追加到结构体中(自动变量的截取只针对 Block 中使用的自动变量)
所谓“截取自动变量”意味着在执行 Block 语法时,Block 语法表达式所使用的自动变量值被保存到 Block 的结构体实例(即 Block 自身中),但无法实现改写截取变量的值
__block 说明符
想在 Block 中保存值,有两种方法:
- 使用静态变量/静态全局变量/全局变量 对静态全局变量和全局变量的访问与转换前完全相同,但静态变量则是通过指针对其进行访问,将其指针传给结构体并保存
- 使用 __ block,__ block 变量实际是结构体类型的自动变量,持有指向该实例自身的 __ forwarding 指针,通过 __forwarding 指针来访问成员变量(即原自动变量)
Block 存储域
Block 和 __block 变量实质上都是在栈上的结构体实例,Block 的类别有 _NSConcreteStackBlock,_NSConcreteGlobalBlock,_NSConcreteMallocBlock
三者分别储存在栈,数据区域(.data区),堆中,目前出现的都是在栈上的,但在记述全局变量的地方使用 Block 时,生成的是 _NSConcreteGlobalBlock 类对象,当 Block 语法表达式中不使用应截获的自动变量时,也会被设置在数据区域
Block 超出变量作用域可存在的原因:Blocks 提供了将 Block 和 __ block 变量从栈上复制到堆上的方法来解决这个问题,变量作用域结束时,栈上的 Block 和 __ block 被废弃,但堆上的不受影响,而 __ forwarding 指针则指向堆上的结构体实例,使得能够正确访问 __block 变量
将 Block 作为函数返回值返回时,会自动生成复制到堆上的代码,除此之外,需要使用 copy 方法
编译器不能进行判断的情况:
-
向方法或函数的参数中传递 Block 时
但如果在方法/函数中适当复制了传递来的参数,就不需在调用之前手动添加了,以下方法/函数不需要手动复制:
-
Cocoa 框架的方法且方法名中含有 usingBlock 等时
-
Grand Central Dispatch 的 API
Block的类 | 副本源的配置储存域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 复制到堆 |
_NSConcreteGlobalBlock | 数据区域 | 不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
ARC 有效时多次调用 copy 方法也没有问题
__block 变量存储域
__block 变量的配置存储域 | Block 从栈复制到堆时的影响 |
---|---|
栈 | 复制到堆并被Block持有 |
堆 | 被Block持有 |
在栈上的 __ block 变量用结构体实例在 __ block 变量从栈复制到堆上时,会将 __ forwarding 的值替换为复制目标堆上的 __ block 变量用结构体的位置,以达到顺利访问同一个 __block 变量的目的
__block int val = 0;
void (^blk)(void) = [^{++val} copy]; //使用初始化好的 __block 变量
++val; //使用与Block无关的变量
blk();
NSLog(@"%d", val);
//均可转换为:
//++(val.__forwarding->val);
截获对象
被截取的对象是 Block 用的结构体中附有 __strong 修饰符的成员变量,并通过copy 和 dispose 函数来使 Block 用结构体持有或释放该对象,在 Block 从栈复制到堆时及堆上的 Block 被废弃时会调用这些函数。会复制到堆的情况:
- 调用 copy
- 作为函数返回值
- 将 Block 赋值给附有 __strong 修饰符的 id 或 block 类型时(编译器自动调用 copy)
- 在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递 Block 时
因此,Block 中使用的赋值给附有 __ strong 修饰符的自动变量的对象和赋值到堆上的 __ block 变量由于被堆上的 Block 持有,因而可超出其变量作用域而存在。(只有调用了 copy 函数才能持有捕获的附有 __strong 修饰符的对象类型的自动变量)
__ block 变量为附有 __ strong 修饰符的 id 类型的自动变量时,和在 Block 中使用赋值给 __strong 修饰符的对象自动变量的对象相同,(通过相似的函数持有和释放)
如果在 Block 中使用附有 __ weak 修饰符的 id 变量,可正常执行,作用域结束时 nil 被赋值给该变量。若同时指定 __ block 和 __ weak,执行结果一样
Block 循环引用
类对象的 Block 类型成员变量中使用 self 或其他成员变量时会形成循环引用
-
可声明 __weak 修饰符的变量,并将 self 赋值使用
-
使用 __ block 变量来避免,并在 Block 语法表达式中将 __block 变量赋值 nil
但如果不执行该 Block,就会循环引用并引起内存泄漏
优:通过 __block 可控制对象的持有期间
缺:必须执行 Block
网友评论