1 Blocks概要
1.1 什么是Blocks
Blocks是C语言的扩充功能,带有局部变量的匿名函数,相当于C++中的Lambda表达式。
Blocks 提供了类似由C++和Objective-C 类生成实例或对象来保持变量值的方法,其代码量与编写C 语言函数差不多。
C++和Objective-C要想保持变量的值,必须要使用类、静态变量、全局变量,但是代码量太多。而Blocks则相当于函数。
程序语言 | Block的名称 |
---|---|
C+ Blocks | Block |
Smalltalk | Block |
Ruby | Block |
LISP | Lambda |
Python | Lambda |
C++ 11 | Lambda |
Javascript | Anonymous function |
2 Blocks模式
2.1 Block语法
1 | ^ 返回值类型 参数列表 表达式 |
“返回值类型”可省略。
若没有参数,“参数列表”可省略。
2.2 Block类型变量
类型为Block的变量,可相互赋值
作用:自动变量、函数参数、静态变量、静态全局变量、全局变量
1 | int (^blk)(int) = ^(int counts){return count+1;}; |
由于Block类型变量在函数参数和返回值中使用时,记述方式极为复杂,可用typedef来解决:typedf int (^blk_t)(int);
则在使用时:void func(int (^blk)(int)){}
==>>void func(blk_t blk){}
int (^func() (int)){}
==>>blk_t func(){}
有Block类型变量,也有Block指针类型变量:
1 | blk_t *blkptr = &blk; //Block指针定义 |
2.3 截获自动变量值
Block语法保存的是局部变量的瞬时值,之后即使该变量改变,Block中的变量值也不变。
1 | int val = 10; |
2.4 __block说明符
在Block块中无法改变被捕获的变量的值。
若想在Block中改变该变量的值,需要给其附加上__block
说明符。
1 | __block int val = 0; |
2.5 截获的自动变量
给截获的自动变量赋值会报错,但是调用截获的自动变量的自身的更改其对象的方法则不会报错。
截获C语言的数组时,也会报错,可用指针来解决。
3 Blocks的实现
3.1 Block的实质
Block即为Objective-C的对象
clang (LLVM编译器)通过“-rewrite-objc ” 选项就能将含有Block 语法的源代码变换为C++的源代码(是使用了struct 结构的C 语言源代码)。
1 | //Block语法 |
3.2 截获自动变量值
Block语法中使用的自动变量会追加到__main_block_impl_0结构体中
1 | int main(){ |
Block的匿名函数^{printf(fmt, val)}
会被转换为:
1 | static void __main_block_func_0(struct __main_block_impl_0 *__cself){ |
3.3 __block说明符
Block中不能改变截获的自动变量,可用“__block存储域说明符”来解决。
C语言中的存储类说明符有:typedef, extern, static, auto, register
__block
将该变量变成栈上生成的__Block_byref_val_0结构体类型的变量,相当于该结构体持有原变量。
访问__block变量:__Block_byref_val_0结构体有个成员变量__forwarding,__forwarding变量指向实例自身,通过它来访问__block变量的值。
3.4 Block存储域
3.4.1 Block与__block变量
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block变量 | 栈上__block变量的结构体实例 |
3.4.2 Block的类及其存储域
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock |
栈 |
_NSConcreteGlobalBlock |
程序的数据区域(.data 区) |
_NSConcreteMallocBlock |
堆 |
3.4.2.1 _NSConcreteGlobalBlock
在全局变量处使用Block时,或者Block在函数内但不截获自动变量时,生成的Block是_NSConcreteGlobalBlock
对象,Block结构体的成员变量isa 的初始化:impl.isa = &_NSConcreteGlobalBlock;
3.4.2.2 _NSConcreteMallocBlock
设置在栈上的Block和__block变量在作用域结束时会被废弃,通过将其复制到堆上可解决此问题。
1.复制到堆上的Block 将_NSConcreteMallocBlock类对象写入Block 结构体实例的成员变量isa。
2.__block 变量通过结构体成员变量__forwarding ,只要__forwarding 指向堆上的结构体实例,就可以实现无论__block 变量配置在栈上还是堆上时都能够正确地访问__block 变量。
3.4.2.3 何时栈上的Block 会复制到堆?
- 调用Block 的copy 实例方法时。
- Block 作为函数返回值返回时。
- 将Block 赋值给附有__strong 修饰符id 类型的类或Block 类型成员变量时。
- 在方法名中含有usingBlock 的Cocoa 框架方法或Grand Central Dispatch 的API 中传递Block 时。
大多数情况下编译器会适当地进行判断(如上述2、3、4),自动调用_Block_copy
函数将Block从栈复制到堆上。
3.4.2.4 调用copy
方法手动将Block复制到堆
向方法或函数的参数中传递Block 时,编译器不能判断是否自动生成复制代码,需要手动生成:
使用第一章中的copy
方法:blk = [blk copy];
或者[^{···} copy]
但是如果在方法或函数中适当地复制了传递过来的参数,那么就不必在调用该方法或函数前手动复制了。如以下方法不用手动复制。
- Cocoa 框架的方法且方法名中含有usingBlock 等时。
- Grand Central Dispatch 的API。
对于己在堆上或者数据区的Block ,调用copy 方法又会如何呢?
Block的类 | 副本原存储位置 | 复制效果 |
---|---|---|
_NSConcreteStackBlock |
栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock |
程序的数据区域 | 什么都不做 |
_NSConcreteMallocBlock |
堆 | 引用计数增加 |
不管Block 配置在何处,用copy 方法复制都不会引起任何问题。在不确定时调用copy方法即可。
3.5 __block变量存储域
3.5.1 一个Block 中使用__block 变量
若在一个Block 中使用__block 变量,则当该Block 从栈复制到堆时,使用的所有__block变量也全部被从栈复制到堆。 此时, Block 持有__block 变量。
即使在该Block 己复制到堆的情形下,复制Block 也对所使用的__block 变量没有任何影响。
__block变量的存储域 | 从栈复制到堆上的影响 |
---|---|
栈 | 从栈复制到堆,并被Block持有 |
堆 | 被Block持有 |
3.5.2 多个Block 中使用__block 变量
在多个Block 中使用__block 变量时,因为最先会将所有的Block 配置在栈上, 所以__block变量也会配置在栈上。
在任何一个Block 从栈复制到堆时, __block 变量也会一并从栈复制到堆并被该Block 所持有。
当剩下的Block 从栈复制到堆时,被复制的Block 持有__block 变量,并增加__block 变量的引用计数。
如果配置在堆上的Block 被废弃,那么它所使用的__block 变量也就被释放。
3.5.3 为何不管__block 变量配置在栈上还是在堆上,都能够正确地访问该变量?
在__block 变量从栈复制到堆上时, 会将成员变量__forwarding 的值替换为堆上的__block 变量结构体实例的地址。
3.6 截获对象
通过使用附有__strong 修饰符的自动变量, Block 中截获的对象就能够超出其变量作用域而存在。
通过BLOCK_FIELD_IS_OBJECT
和BLOCK_FIELD_IS_BYREF
参数,区分copy 函数和dispose 函数的对象类型是对象还是__block 变量。
3.7 __block变量与对象
3.8 Block循环引用
若一个持有Block的对象,当他从栈复制到堆上后,Block将持有该对象,导致循环引用。
1 | //持有Block的对象 |
编译器会检测出循环引用。
使用__block变量的优点:
- 通过__block 变量可控制对象的持有期间。
- 在不能使用__weak 修饰符的环境中不使用__unsafe_unretained 修饰符即可(不必担心悬垂指针)。
- 在执行Block 时可动态地决定是否将nil 或其他对象赋值在__block 变量中。
使用__block变量的缺点:必须执行Block。
3.9 copy/release
ARC 无效时,一般需要手动将Block 从栈复制到堆。用
copy
实例方法来复制.用release
实例方法来释放。
只要Block 有一次复制并配置在堆上,就可通过retain
实例方法持有该Block。
对于配置在栈上的Block,使用copy
实例方法来持有Block。
ARC 无效时,使用__block 说明符来避免Block 中的循环引用。
当Block 从栈复制到堆时,若Block 使用的变量为附有__block 说明符的id 类型或对象类型的自动变量,不会被retain ;若不是,则被retain。
且此时不需要执行Block。