3.再来看看最终调用Block的时候和没有截获的区别
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const charchar *ch = __cself->ch; // bound by copy int b = __cself->b; // bound by copy printf(ch,b); }
可以看出block()这句代码转换的调用方式没有任何变化 还是转换为一下这句话,而且把本身结构体作为参数进行了传递
(*block-impl.FuncPtr)(block)
现在注意看__main_block_func_0这个C语言的函数,和之前相比这里对传递过来的block结构体参数进行了变量获取__cself->ch 和 __cself->b(这两个变量已经在Block表达式之前进行了声明定义),并最终打印出来
但是,Block中所捕获的变量就犹如“带有局部变量值的匿名函数”所说,仅仅截获局部变量的值而已,如果在截获的Block里面重写局部变量也不会改变原先所截获的局部变量
block 引用外部对象时候,不是简单的指针引用(浅复制),而是一种重建(深复制)方式(括号内外分别对于基本数据类型和对象分别描述)
所以如果在 block 中对外部对象进行修改,无论是值修改还是指针修改,自然是没有任何效果
例如:
int a = 0; void (^block)(void) = ^{a = 1};
这样写直接就编译出错了!!!
可以直接看__block_main_block_impl_0的实现上,并不能改写其捕获变量的值,因此直接报错了
综上所述,所谓的“截获自动变量”,无非就是在执行Block语法的时候,Block语法所用到的局部变量值被保存到Block的结构体实例当中(即所谓的Block本体)
5.要在 Block 中改变里面的值
第一种方法:运用C语言中的变量
- 静态变量
- 静态全局变量
- 全局变量
int global_val = 1; // 全局变量 static int static_global_val = 2; // 静态全局 int main(){ static int static_val = 3; // 静态局部 void (^block)(void) = ^{ global_val *= 1; static_global_val *= 2; static_val *= 3; }; block(); }
这里全局和静态全局问题不大,出了作用于照样能访问,但是静态局部的话如果去掉Static按上面那种写法,就直接报错了,原因就是实现上不能改变捕获的局部变量的值。
没错,应该能想到了,能改变的情况就是直接让Block截获局部变量的指针,看Clang的源码
int global_val = 1; static int static_global_val = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; intint *static_val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, intint *_static_val, int flags=0) : static_val(_static_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { intint *static_val = __cself->static_val; // bound by copy global_val *= 1; static_global_val *= 2; (*static_val) *= 3; } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(){ static int static_val = 3; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); printf("%d\n",global_val); printf("%d\n",static_global_val); printf("%d\n",static_val); }
很简单就能看出区别了
- 在__main_block_impl_0这个结构体中的追加的成员变量变成了int *_static_val指针了
- 在结构体实例化函数中__main_block_impl_0参数也变为了&static_val地址了
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { intint *static_val = __cself->static_val; // bound by copy global_val *= 1; static_global_val *= 2; (*static_val) *= 3; }
根据Block语法转换而来的C静态函数,使用static_val的指针进行访问,上面参数可以看出,初始化的时候将静态变量的static_val指针传递给__main_block_impl_0结构体进行追加成员变量初始化并保存,这样做也是对超出作用于使用变量的最简单的方法
第二种方法:就是大家所熟知的__block修饰符的使用
__block int a = 0; void (^block)(void) = ^{a = 1};
Clang 转化后代码
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 1; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(){ __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0}; void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); return 0; }
代码多了不少,分解一下
__block int a = 0;
Clang 的代码如下
__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),0};
这货竟然加了_block变成了结构体实例,在栈上生成了__block_byref_a_0的结构体实例,a变量初始化为0,这个值也出现在了这个结构体成员变量变量中,意味着该结构体持有相当于原局部变量的成员变量
struct __Block_byref_a_0 { *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
这个初始化的原结构,里面的最后一个int a就是原局部变量的成员变量
void (^block)(void) = ^{a = 1};
分解如下
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
还是调用__main_block_impl_0的初始化结构体,由于int a加了__block修饰符,那么这个初始化函数传递的不再是&a的地址,而是换成__Block_byref_a_0这个结构体实例的指针进行传递(该结构体其实也是个对象,最后的属性放着需要截获的局部变量)
这里的__main_block_func_0对应的block块函数转换为C
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 1; }
看了两个方案,第一个用C语言的静态变量来解决,那么直接操作静态变量的指针就完事了,但是__block要比这个真的复杂多了。Block的结构体实例持有指向__block变量的 __Block_byref_a_0结构体实例的指针。
__Block_byref_a_0这个结构体实例的成员变量__forwarding持有指向该结构体实例自身的指针。通过__forwarding访问成员变量a。
另外提一点,__block内部的__Block_byhef_val_0这个也是独立的结构体实例,不属于__main_blocl_impl_0结构体中,这样方便了在多个block中都能访问__block变量。
小结一下,不然太乱:
1.普通的局部变量被Block截获只是单纯的截获其值,并追加到__main_block_impl_0结构体中(无法修改重写)
2.要允许Block修改值有两个方法,第一个就是用C语言的变量,代表就是静态局部变量,第二个就是__block修饰符
3.__block修饰的变量,其实这就是变成了个对象结构体__Block_byref_val_0,初始化__main_block_impl_0的时候追加的是__block生成的结构体指针,例如__block int a = 0;这个int不在是普通的数据类型,而是在标识符的引用下变成了对象
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
而int的值就存储在这个对象的一个字段 a中,而Block截获的就是这个结构体对象的指针,可以随意修改结构体内部a的值
4.最终在__main_block_func_0函数中调用__block结构体指针的forwarding指针(这一节指向自己,后面会变)的成员变量a来进行重新赋值
5.其实__block这方法生成的源码,能大致看出来这其实类似OC里面对象的retain和release一系列操作