Objc 中的 Block 其实是一个对象,之前也说过 Objc 中对象的结构
来看下 Block 的实现,新建 TooT.m
文件写一个 func_TooT
函数:
1 2 3 4 5 6 7 8 9
| void func_TooT(void) { int tt_value = 0; void (^bt)(void); bt = ^(){ printf("%d",tt_value); }; bt(); }
|
然后在终端中执行 clang -rewrite-objc TooT.m
查看 C++ 的实现。
打开 TooT.cpp
文件,直接搜索 func_TooT
,得到以上代码的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| struct __func_TooT_block_impl_0 { struct __block_impl impl; struct __func_TooT_block_desc_0* Desc; int tt_value; __func_TooT_block_impl_0(void *fp, struct __func_TooT_block_desc_0 *desc, int _tt_value, int flags=0) : tt_value(_tt_value) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __func_TooT_block_func_0(struct __func_TooT_block_impl_0 *__cself) { int tt_value = __cself->tt_value;
printf("%d",tt_value); }
static struct __func_TooT_block_desc_0 { size_t reserved; size_t Block_size; } __func_TooT_block_desc_0_DATA = { 0, sizeof(struct __func_TooT_block_impl_0)}; void func_TooT(void) { int tt_value = 0; void (*bt)(void); bt = ((void (*)())&__func_TooT_block_impl_0((void *)__func_TooT_block_func_0, &__func_TooT_block_desc_0_DATA, tt_value)); ((void (*)(__block_impl *))((__block_impl *)bt)->FuncPtr)((__block_impl *)bt); }
|
以上内容其实是四部分:
- 该 Block 的结构(数据)
- 该 Block 的执行的动作
- 该 Block 的信息,比如所占空间大小
func_TooT
函数
Block 的结构
从中可以看到 Block 被编译器重写为以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __func_TooT_block_impl_0 { struct __block_impl impl; struct __func_TooT_block_desc_0* Desc; int tt_value; __func_TooT_block_impl_0(void *fp, struct __func_TooT_block_desc_0 *desc, int _tt_value, int flags=0) : tt_value(_tt_value) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
|
不难看出,一个完整的 Block 有以下 6 部分:
- isa:指向类型地址,实现对象的功能
- Flags:
- Reserved:保留变量
- FuncPtr:Block 要执行的动作函数地址
- Desc:Block 的描述信息
- Variables:Block 引用外部的同名同类型变量,在内部拷贝了一份;可能有多个
Block 的动作
Block 要执行的动作保存在 __func_TooT_block_func_0
函数中,Block 自身会保存该函数的地址,执行的时候将 Block 自身作为参数传进来,类似于 Objc 中对象的方法默认第一个参数就是对象本身 self
。
在 Block 中取出被 Block 捕获拷贝的变量,执行在 Block 定义的行为。
Block 的描述
只有两个字段: reserved
、Block_size
,保留字段和 Block 的size
Block 的初始化
第四部分就是 void func_TooT(void)
,Block 被初始化去掉类型强转可以被简化为:
1
| bt = &__func_TooT_block_impl_0(__func_TooT_block_func_0, &__func_TooT_block_desc_0_DATA, tt_value));
|
其中三个参数分别为 Block 动作函数地址,附加信息 和 所捕获的变量。
其中 动作函数地址 就是上面所说 Block 要执行的代码,附加信息默认是 {0, sizeof(struct block)},所捕获的变量在这里只是一个,如果捕获多个时就会有多个参数。
总结
Block 也是一个对象,除了有指向自身类型的 isa 指针,本身还有一个函数指针指向自身要执行动作的函数地址,该函数以 Block 本身为参数,Block 本身还会存储一份引用外部变量的同名同类型的变量(可能为多个),这样才执行的时候可以获取到所引用变量的值。
__block 的实现原理
上面说过 Block 捕获变量是以形参的形式传进一个函数被 Block 本身所持有,这也是在 Objc 中值类型只能被 Block 捕获读取,却不能修改,但是给变量加上 __block
修饰符就可以,看下编译器是怎么实现的,来段代码:
1 2 3 4 5 6 7 8 9
| void func_TooT(void) { __block int tt_value = 1021; void (^bt)(void); bt = ^(){ tt_value++; }; bt(); }
|
用 clang -rewrite-objc TooT.m
看 C++ 代码发现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct __Block_byref_tt_value_0 { void *__isa; __Block_byref_tt_value_0 *__forwarding; int __flags; int __size; int tt_value; }; static void __func_TooT_block_func_0(struct __func_TooT_block_impl_0 *__cself) { __Block_byref_tt_value_0 *tt_value = __cself->tt_value;
(tt_value->__forwarding->tt_value)++; } void func_TooT(void) { __attribute__((__blocks__(byref))) __Block_byref_tt_value_0 tt_value = {(void*)0,(__Block_byref_tt_value_0 *)&tt_value, 0, sizeof(__Block_byref_tt_value_0), 1021}; void (*bt)(void); bt = ((void (*)())&__func_TooT_block_impl_0((void *)__func_TooT_block_func_0, &__func_TooT_block_desc_0_DATA, (__Block_byref_tt_value_0 *)&tt_value, 570425344)); ((void (*)(__block_impl *))((__block_impl *)bt)->FuncPtr)((__block_impl *)bt); }
|
可以看到, 相对于上面的代码,只多了 __block
修饰词,int
类型就被编译器重写为一个结构体,结构体中的属性中有一个 __forwarding
指针,还有一个同名同类型变量。
看初始化结构体的时候把自身地址赋值给 __forwarding
,这样即便结构体被当做形参传给 Block,Block 也是可以通过结构体的 __forwarding
指针指向的结构体的同名同类型变量取到真正的变量,只能说这个思路实现的真好~
Block 的动作函数中我们也可以看到:
1 2
| (tt_value->__forwarding->tt_value)++;
|