- Block原理
从源码说起
int main(int argc, char * argv[]) {
@autoreleasepool {
int a = 0;
void (^block)(void) = ^{
NSLog(@"%d",a);
};
block();
return 0;
}
}
翻译成底层C++之后
int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl*)block);
分解第一步:__main_block_impl_0结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这里调用了他的构造函数,
第一个参数__main_block_func_0会创建一个__main_block_func_0静态函数,这个函数中会生成block中会使用到的block变量,然后将这个静态函数保存到了__block_impl
第三个参数会从block外将需要的变量赋值到block块的结构体内。
因此:
1. block外部的变量a在后续无论做什么操作,都不会影响block内部保存的变量a
2. 这就是在block内部直接修改自动变量会报错的原因,
3. 如果这里直接允许修改了,在目前条件下,也仅仅能做到block内部的变量a进行重新赋值操作,和外部变量a没有关系,产生歧义。
分解第二步:调用结构体
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl*)block);
block指针指向的是__main_block_impl_0 的首地址,即__block_impl的地址,所以可以强转为(__block_impl *)类型,并访问其成员FuncPtr,指向的是静态函数地址,并传入参数__main_block_impl_0,也就是block自己。
总结:
名称 | 类型 | 是否随block内容改变 | 生成顺序 | 说明 |
---|---|---|---|---|
__block_impl | 结构体 | NO | 1 | 底层结构体,属于__main_block_impl_0成员 |
__main_block_impl_0 | 结构体 | YES | 2 | 缓存变量/对象,主结构体 |
__main_block_func_0 | 静态函数 | YES | 2 | 缓存代码,地址存放在__block_impl中 |
将外部变量/对象的信息缓存在__main_block_impl_0中,
将代码缓存在静态函数中,
静态函数在缓存代码的时候需要用到外部变量/对象的信息
执行block就是执行了该静态函数