<font color = 'gray'>2018-10-26 编辑 :yzl </font>
1. block定义
用Apple文档的话来说,A block is an anonymous inline collection of code, and sometimes also called a "closure".
关于闭包,我觉得阮一峰的一句话解释简洁明了:闭包就是能够读取其它函数内部变量的函数。
这个解释用到block来也很恰当:一个函数里定义了个block,这个block可以访问该函数的内部变量
2. Block数据结构
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
blockStruct.png
在objc中,根据对象的定义,凡是首地址是*isa的结构体指针,都可以认为是对象(id)。这样在objc中,block实际上就算是对象。
其结构体成员如下:
- isa,指向所属类的指针,也就是block的类型
- flags,标志变量,在实现block的内部操作时会用到
- Reserved,保留变量
- FuncPtr,block执行时调用的函数指针
可以看出,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。
<font color = red> 重点理解以下话:</font>
<font color = #855e42>编译器会根据block捕获的变量,生成具体的结构体定义。block内部的代码将会提取出来,成为一个单独的C函数。创建block时,实际就是在方法中声明一个struct,根据捕获到的变量生成相应的成员变量,并且用捕获到变量的值初始化该struct的成员。而执行block时,就是调用那个单独的C函数,并把该struct指针做为函数参数传递过去。</font>
int main(int argc, char * argv[]) {
@autoreleasepool {
int a = 0;
void(^block)(void) = ^{
int b = a;
};
block();
a ++;
return 0;
}
}
clang (clang -rewrite-objc main.m)之后
blockclang.png
自动变量val虽然被捕获进来了,但是是用 __cself->val
来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。所以在__main_block_func_0这个函数中即使我们重写这个自动变量val的值,依旧没法去改变Block外面自动变量val的值。
3. Block 类型
block中的isa指向的是该block的Class。在block runtime中,定义了6种类:
_NSConcreteStackBlock 栈上创建的block
_NSConcreteMallocBlock 堆上创建的block
_NSConcreteGlobalBlock 作为全局变量的block
_NSConcreteWeakBlockVariable
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock
其中我们能接触到的主要是前3种,后三种用于GC不再讨论
4. __block类型的变量
默认block捕获到的变量,都是赋值给block的结构体的,相当于const不可改。为了让block能访问并修改外部变量,需要加上__block
修饰词。
加了个__block
,被捕获的变量会生成了一个struct(struct __Block_byref)
。这个struct的首地址同样为*isa。
举个例子:
原代码:
用clang编译结果
71A1BB78-95EF-431D-B7CD-E5E131F2D65D.png
从上面编译结果可以看出,__block
修饰的局部变量a,被生成一个·__Block_byref_a_0
结构体,并且拷贝一份到堆上,关系图如下:
之后对a操作都是对堆中a值操作,如 (a.__forwarding->a) ++
,在block 打印a的值,也是(a.__forwarding->a)
从而达到了改变a值的目的。
5. 结束语
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址,全局变量,全局静态变量以及静态变量之所以能修改,是因为这些变量都不是在栈上。__block
所起到的作用就是只要观察到该变量被 block所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
网友评论