- 简介
- 声明方式
- 无参数且无返回值
- 无参数但有返回值
- 有参数但无返回值
- 有参数且有返回值
- block的参数为block
- block的返回值为block
- 一个更复杂的block声明
- 优雅的写法:
- 变量捕捉
- 只读变量
- __main_block_impl_0说明
- __main_block_func_0说明
- __block_impl说明
- 总结
- 读写变量
- 内存管理
- 链式编程
简介
block是Apple对c语言实现的一种扩展,本文主要针对ARC模式下的block实现进行探究.
声明方式
可以按照定义函数指针的方式进行block的定义,然后将解引用操作符*
替换为^
即可。按照有无返回值以及是否存在参数,将block的声明分为以下三种类型。同时,根据block所处的位置不同(property、方法参数、方法返回值以及临时变量),block的声明形式也不太一样。
无参数且无返回值
1 - (void)func {
2 void (^raw)(void);
3 }
这里声明了一个临时变量raw
,类型为:void (^)(void)
。可以这样进行语言描述:声明了一个block变量raw,该变量无形参且无返回值。
这种形式最简单,系统专门做了两个typedef来简化这种写法:
1 typedef void (^dispatch_block_t)(void);//常用;
2 typedef void (^os_block_t)(void);//不常用;
接下来的几种block形式都可以在此声明形式的基础上扩展出来,如下所示:
无参数但有返回值
1 - (void)func {
2 int (^raw)(void);
3 }
有参数但无返回值
1 - (void)func {
2 void (^raw)(int);
3 }
有参数且有返回值
1 - (void)func {
2 int (^raw)(int);
3 }
以上声明方式可以称为简单block声明方式,在开发过程中,或者阅读第三方源码时经常再到一些复杂的声明,这举几个例子:
block的参数为block
1 - (void)func {
2 void (^paramraw)(void (^)(char));
3 }
该paramraw接收一个void (^)(char)
型的参数,且无返回值。直接声明这种类型的block的技巧性很强。这里,我们从void (^)(char)
类型入手,一步步完成该paramraw的声明。
- 首先声明
void (^paramraw)(void)
- ;
- 将参数的
void
- 替换为
void (^)(char)
- 类型即可,此时便得到了上述声明。
block的返回值为block
1 - (void)func {
2 int (^(^returnraw)(long))(char);
3 }
这里的returnraw接受一个long
型的参数,且返回一个block(记作rblock
),rblock
接受一个char
型的形参并且返回一个int
类型的值。这里依然基于void (^returnraw)(long)
来做调整。
- 首先将返回类型
void
- 替换为
^
- ,得到
^(^returnraw)(long)
- ,此时即表明了
returnraw
- 返回的是一个block;
- 紧接着,基于
^(^returnraw)(long)
- ,我们为其添加参数,得到
(^(^returnraw)(long))(char)
- ;
- 最后,我将为
^(^returnraw)(long)
- 添加返回值类型,得到
int (^(^returnraw)(long))(char);
一个更复杂的block声明
1 - (void)func {
2 unichar (^(^(^complexraw)(int (^(^paramblock)(char))(size_t)))(NSEdgeInsets))(CGRect) = ^(int (^(^pb)(char))(size_t st)) {
3 unichar (^(^tmp)(NSEdgeInsets))(CGRect);
4 return tmp;
5 };
6 }
complexraw
的定义看起来较为复杂,其实实现也很简单,按照上面的例子依次对void (^complexraw)(void)
的参数项及返回值项作替换即可。
优雅的写法:
block的声明方式有时候的确让人不知所措,就连Apple自家的工程师也在工程代码中写注释称:
// I use the following typedef to keep myself sane in the face of the wacky Objective-C block syntax.
typedef void (^ChallengeCompletionHandler (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential);
平时开发过程中,对于复杂些的类型声明,建议全部使用typedef进行类型命名。避免出现上一个示例中的情况
变量捕捉
使用block时,若在block内部使用到了外部声明的变量,那么此时便会发现变量捕获。根据block是否修改了变量值,我们将其分为只读变量
与读写变量
两种类型
只读变量
block内部对外部变量只有读操作,没有写操作,此时,在block的底层实现上,只会将一个参数值传递block结构体的构造函数。
1 int main(int argc, char const *argv[]) {
2 int a = 8372;
3 void (^block)(void) = ^(void){
4 printf("hello world: %d\n", a);
5 };
6 block();
7 return 0;
8 }
该示例中,block
内部对变量a
只有读操作。通过-rewrite-objc
获取的.cpp
源文件中,我们可以看到如下代码:
1 int main(int argc, char const *argv[])
2 {
3 int a = 8372;
4 void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
5 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
6 return 0;
7 }
这里比较重要的有__main_block_impl_0
,__main_block_func_0
,__block_impl
,另外,我们可以看到block
在这里被重写成了函数指针。
__main_block_impl_0说明
1 struct __main_block_impl_0 {
2 struct __block_impl impl;
3 struct __main_block_desc_0* Desc;
4 int a;
5 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
6 impl.isa = &_NSConcreteStackBlock;
7 impl.Flags = flags;
8 impl.FuncPtr = fp;
9 Desc = desc;
10 }
11 };
从这里我们知道__main_block_impl_0
是一个结构体类型。这里可以发现int a
字段,同时在结构体的构造函数中,也通过初始化参数列表的形式将_a
赋值给字段a
。同时为impl.FuncPtr
赋值了函数指针,在这block执行的时候会进行调用。
__main_block_func_0说明
1 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
2 int a = __cself->a; // bound by copy
3 printf("hello world: %d\n", a);
4 }
该静态方法可以看作是block的执行体的实现。这里首先获取取了__main_block_impl_0
的a
字段,并将a
进行了输出
__block_impl说明
1 struct __block_impl {
2 void *isa;
3 int Flags;
4 int Reserved;
5 void *FuncPtr;
6 };
这里将该结构体看作是block的具体实现,其中isa
指针,这也表明了block中对象类型。另外,FuncPter
指向的是block的执行体,该示例中,其指向__main_block_func_0
.
总结
重写的cpp文件的整体流程大致如下:
- 定义变量
int a = 8372
- 声明一个函数指针
block
- , 该函数指针的参数以及返回值类型与定义的
block
- 类型相同。
- 创建
__main_block_impl_0
- 的实例,通过它的构造函数,将
__main_block_func_0
- 的地址赋值给了字段
impl.FuncPtr
- ,将
__main_block_desc_0_DATA
- 的地址赋值给了字段
Desc
- ,同时,将我们外部声明的变量
a
- 赋值给了字段
a
- ,这里是值传递,所以内部无法对外部的变量
a
- 进行修改。另外,该结构体的形参
flags
- 带有默认参数
0
- ,所以在
main
- 函数中未看到其参数传递的过程。
- 通过前3步,已经准备好了block执行所需要的环境。最后一步,经过一系列的类型转换取出
block->FunPtr
- 并执行。
- 第3、4步中涉及到大量的类型的转换,这里也需要具备结构体的内存布局相关的知识。
读写变量
在block内部写外部定义的变量时,需要对变量添加__block
修饰符,面试的过程中,可能会问到__block
的实现原理,这里只需要将其转换后的cpp
源码理解了即可。
示例如下:
1 int main(int argc, char const *argv[])
2 {
3 __block int a = 132;
4 printf("1a的地址: %p\n", &a);
5 void (^block)(void) = ^(void){
6 printf("0a的地址: %p\n", &a);
7 a = 10;
8 printf("hello world: %d\n", a);
9 };
10 block();
11 printf("2a的地址: %p\n", &a);
12 return 0;
13 }
这里,打印出了不同阶段变量a
的地址。同样的,通过重写,得到的cpp
中的关键代码如下:
1 int main(int argc, char const *argv[])
2 {
3 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 132};
4 printf("1a的地址: %p\n", &(a.__forwarding->a));
5 void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
6 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
7 printf("2a的地址: %p\n", &(a.__forwarding->a));
8 return 0;
9 }
相比于只读操作,这里多了一个__Block_byref_a_0
,并且__main_block_impl_0
的构造函数有及内部字段均发生了改变。关键的部分在于主函数将变量a
包装成了一个__Block_byref_a_0
结构体,并且将a
的地址以及保存起来,这样,在修改变量a
的时候,实际会通过__Block_byref_a_0
指针找到该内存区域,之后再取出其中的a
将其修改。
内存管理
这里的内存管理主要是指循环引用导致的内存泄漏问题。当block有引用对象自身时(包括对象自身的属性\字段),不论是只读还是读写,都会捕获对象,这样就形成了循环引用。一般情况下,我们会通过__weak typeof(self) _weakSelf = self;
来创建一个基于self
的弱引用,然后在block内部通过_weakSelf
做一些额外的操作。另外,为保证在block执行期间内_weakSelf
不被销毁,我们也可以在block内部通过__strong typeof(_weakSelf) _strongSelf = _weakSelf;
来强引用self
。
事实上,某些block内部完全可以大胆的使用self
,如下面的情况:
1 @class QTCalculator;
2 typedef QTCalculator *(^CalculatorMaker)(int);
3 @interface QTCalculator : NSObject
4
5 @property (nonatomic, assign, readonly) int result;
6
7 - (CalculatorMaker)add;
8
9 @end
10
11 @implementation QTCalculator
12
13 - (CalculatorMaker)add {
14 return ^(int x) {
15 self.result += x;
16 return self;
17 };
18 }
19
20 @end
该类的实现部分在block中直接引用了self
,但并不会造成循环引用。这是因为add
并没有被类QTCalculator
的实例所引用,这样就不会造成实例中引用block
,同时block
中引用实例的现象。类似的还有+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
等系统API,这里的block在执行完成之后会被销毁,所以不会造成循环引用。
如果无法分辨什么情况下使用__weak typeof(self) ws = self;,那么就粗暴的全部以这种方式实施就好。
链式编程
通过block可以方便的实现链式编程。这里通过一个计算器的示例来说明。
1 @class QTCalculator;
2 typedef QTCalculator *(^CalculatorMaker)(float);
3 @interface QTCalculator : NSObject
4
5 @property (nonatomic, copy ) CalculatorMaker add;
6 @property (nonatomic, copy ) CalculatorMaker subtract;
7 @property (nonatomic, copy ) CalculatorMaker multiply;
8 @property (nonatomic, copy ) CalculatorMaker division;
9 @property (nonatomic, assign, readonly) float result;
10
11 @end
12
13 @interface QTCalculator ()
14
15 @property (nonatomic, assign) float result;
16
17 @end
18
19 @implementation QTCalculator
20
21 - (void)dealloc {
22 NSLog(@"dealloc...");
23 }
24
25 - (instancetype)initWithResult:(int)r {
26 self = [super init];
27 self.result = r;
28 return self;
29 }
30
31 - (CalculatorMaker)add {
32 if (_add) {
33 return _add;
34 }
35 __weak typeof(self) ws = self;
36 _add = ^(float x) {
37 __strong typeof(ws) ss = ws;
38 ss.result += x;
39 return ss;
40 };
41 return _add;
42 }
43
44 - (CalculatorMaker)subtract {
45 if (_subtract) {
46 return _subtract;
47 }
48 __weak typeof(self) ws = self;
49 _subtract = ^(float x) {
50 __strong typeof(ws) ss = ws;
51 ss.result -= x;
52 return ss;
53 };
54 return _subtract;
55 }
56
57 - (CalculatorMaker)multiply {
58 if (_multiply) {
59 return _multiply;
60 }
61 __weak typeof(self) ws = self;
62 _multiply = ^(float x) {
63 __strong typeof(ws) ss = ws;
64 ss.result *= x;
65 return ss;
66 };
67 return _multiply;
68 }
69
70 - (CalculatorMaker)division {
71 if (_division) {
72 return _division;
73 }
74 __weak typeof(self) ws = self;
75 _division = ^(float x) {
76 __strong typeof(ws) ss = ws;
77 ss.result /= x;
78 return ss;
79 };
80 return _division;
81 }
82
83 @end
84
85 //main.c中
86 int main(int argc, const char * argv[]) {
87 int status = 0;
88 @autoreleasepool {
89 QTCalculator *calculator = QTCalculator.new;
90 float result = calculator
91 .add(4)
92 .add(5)
93 .multiply(2)
94 .division(3)
95 .subtract(2)
96 .result;
97 NSLog(@"result: %f", result);
98 }
99 return status;
100 }