1 Blocks 概要
1.1 什么是 Blocks
Blocks是 C 语言的扩充功能,Blocks 是带有自动变量(局部变量)的匿名函数。
带有自动变量值的匿名函数 分为 “ 匿名函数” 和 “带有自动变量值”
什么是 “带有自动变量值”:
2 Blocks 模式
2.1 Block 语法
与 C 语言函数定义相比,有两点不同 :1:没有函数名。 2:带有 ^
第一种:
^ int (int count) { return count + 1;};
第二种:
省略返回值类型时:Block 语法将按照 return 语句的类型,返回返回值。
第三种:
2.2 Block 类型变量
“Block” 既指源代码中的 Block 语法,也指由 Block 语法所生成的值。
Block变量声明:
int (^ blk) (int);
Block 类型变量的用途:
(1)自动变量(局部变量)
(2)函数参数
(3)静态变量
(4)静态全局变量
(5)全局变量
使用 Block 语法将 Block 赋值为 Block 类型变量:
int (^blk) (int) = ^(int count) {return count +1 };
在函数参数中使用 Block 类型变量向函数传递 Block:
一种:
void func (int (^blk) (int)) {…}
另一种:
typedef int (^blk_t) (int);
void func (blk_t blk) {…}
在函数返回值中指定 Block 类型,可以将 Block 作为函数的返回值返回:
一种:
- (int (^) (int))func //注:函数返回值为 Block时,返回类型没有 block 变量名
{
return ^(int count) {return count + 1;};
}
另一种:
typedef int (^blk_t) (int);
- (blk_t) func
{
return ^(int count) {return count + 1;};
}
用 typedef 给 block 重命名
用 Block 类型变量调用 Block,与通常的 C 语言变量一样使用:
- (int) methodUsingBlock: (blk_t) blk rate:(int) rate
{
return blk(rate);
}
2.3 截获自动变量值
“带有自动变量值的匿名函数” 中 “带有自动变量值”在 Block 中表现为 “截获自动亦是值”。
Blocks 中,Block 表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为 Block 表达式保存了自动变量的值,所以在执行 Block 语法后,即使改写 Block 中使用的自动变量的值也不会影响 Block 执行时自动变量的值。
2.4 __block 说明符
自动变量值截获只能保存执行 Block 语法瞬间的值。保存后就不能改写该值。尝试改写截获的自动变量值:会生产编译错误
若想在 Block 语法的表达式中将值赋给在 Block 语法外声明的自动变量,需要在该自动变量上附加 __block 说明符。
使用附有 __block 说明符的自动变量可在 Block 中赋值,该变量称为 __block 变量。
2.5 截获的自动变量
截获 OC 对象,调用变更该对象的方法:
结论:赋值给截获的自动变量 arrayM 的操作会产生编译错误,但使用截获的值却不会有问题。
Blokc 截获 C 语言数组:
结论:在 Blocks 中,截获自动变量的方法并没有实现对 C 语言数组的截获。这时,使用指针可以解决该问题。
3 Blocks 的实现
3.1 Block 的实质
Block的实质即为 Objective-C 的对象(结构体)。
结构体包括:
isa类结构指针 (三大类型:_NSConcrete[Stack | Malloc | Global ]Block)
FuncPtr 函数指针
flags , reserved和 Block 截获的自动变量
clang -rewrite-objc源代码文件名
3.2 截获自动变量值 (只对 Block 中使用的自动变量)
截获自动变量值:在执行 Block 语法时,Block 语法表达式所使用的自动变量值被保存到 Block 的结构体实例中。
3.3 __block 说明符
在 Block 中修改自动变量的两种方法:
第一种:用 静态局部变量、静态全局变量、全局变量。
注:静态局部变量:Block 结构体中存放静态局部变量的指针。
第二种:用 __block 说明符 (__block 存储域类型说明符) 类似于 static 、auto 、register 说明符,用于指定变量值的存储到哪个存储域中
__block变量clang后转换为结构体, Block 的结构体实例持有指向 __block 变量的结构体实例的指针。
2.3.4 Block 存储域
Block 转换为 Block 的结构体类型的自动变量,__block 变量转换为 __block 变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。
Block 的类:
_NSConcreteGlobalBlock -存储域:程序的数据区域(.data 区)
(1)记述全局变量的地方使用 Block 语法时
(2)Block 语法的表达式中不使用截获的自动变量时
_NSConcreteStackBlock -存储域:栈 ;复制效果:到堆
除 Global 之外的 Block 生成的 Block 都是栈Block
_NSConcreteMallocBlock -存储域:堆;复制效果:引用计数增加
Blocks提供了将 Block 和 _block 变量从栈上复制到堆上的方法。
这样即使 Block 语法记述的变量作用域结束,堆上的 Block 还可以继续存在。
实际上当 ARC 有效时,大多数情形下编译器会恰当地进行判断,自动生成将 Block 从栈上复制到堆上的代码。
什么时候栈上的 Block 会被复制到堆上:
(1)调用 Block 的 copy 实例方法时
(2)Block 作为函数的返回值时
(2)将 Block 赋值给附有 __strong 修饰符 id 类型的类或Block 类型成员变量时
(3)方法名中含有 usingBlock 的 Cocoa 框架的方法 和 GCD 的 API
需要手动复制的情况:
(1)NSArray 类的initWithObjects
(2)向方法或函数的参数中传递 Block 时(可以不用手动复制)
不管 Block 配置在何处,用 copy 方法复制都不会引起任何问题。在不确定时调用 copy 方法即可。
3.5 __block 变量存储域
Block中使用 __block 变量,当 Block 从栈复制到堆时,使用的所有 __block 变量也全部被从栈复制到堆。
在多个 Block 中使用 __block 变量
Block 超出变量作用域可存在的原因:将 Block 和 __block 变量从栈上复制到堆上
__block 变量用结构体成员变量 __forwarding 存在的原因:实现无论 __block变量配置在栈上还是堆上都能正确地进行访问
3.6 截获对象
__main_block_copy_0 函数使用 _Block_object_assign 函数将对象类型对象赋值给 Block 结构体的成员变量 array 中并持有该对象。
_Block_object_assign 函数调用相当于 retain 实例方法的函数,将对象赋值在对象类型的结构体成员变量中。
__main_block_dispose_0 函数使用 _Block_object_dispose 函数,释放赋值在 Block 用结构体成员变量 array 中的对象。
_Block_object_dispose 函数相当于 release 实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。
__main_block_copy_0 和 __main_block_dispose_0 函数在 Block 从栈复制到堆时以及堆上的 Block 被废弃时调用
3.7 __block 变量和对象
__block 说明符可指定任何类型的自动变量。
3.8 Block 循环引用
在 Block 中使用附有 __strong 修饰符的对象类型自动变量,那么当 Block 从栈复制到堆时,该对象为 Block 所持有。这样容易引起循环引用。
解决方法:
1. ARC :通过 __weak 或 __unsafe_unretained 修饰符(iOS4) 来替代__strong 类型的被截获的自动变量。
2. MRC:通过 __block 说明符指定变量不被 Block 所 retain ; ARC下 __block说明符的作用仅限于使其能在 Block 中被赋值。
原理:
如果对 Block 做一次 copy 操作,Block 的内存就会在堆中
它会将所引用的对象做一次 retain 操作
非 ARC : 如果所引用的对象用了 __block 修饰,就不会做 retain 操作
ARC :如果所引用的对象用了 __unsafe_unretained 或 __weak 修饰,就不会做 retain 操作。
3.9 copy/release
ARC无效时,一般需要手动将 Block 从栈复制到堆。也要释放复制的 Block。
用 copy 方法复制 ,release 方法释放。
注:静态局部变量,静态全局变量,全局变量 的相同与不同点:
相同点:存储区都在全局区
不同点: 作用域不同
全局变量: 其它文件需要用extern关键字再次声明这个全局变量。
静态全局变量:仅所在的文件才可以访问
静态局部变量:仅对其所在的函数体作用域可见。
截获 OC 对象,调用变更该对象的方法: