block - ShenYj/ShenYj.github.io GitHub Wiki
block 主要有三种类型:全局block、堆区block 和 栈区block
OC对象的底层是由 C++ 结构体支撑的,block 也不例外
-
探索代码
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); void (^block)(void) = ^(){ NSLog(@"___func____"); }; block(); } return 0; } -
编译成
C++代码xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o global-block-main-arm64.cpp
-
在
C++代码中提取出关键部分struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_b2997c_mi_1); } 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(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_b2997c_mi_0); void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
通过 main 函数中对 block 的定义和执行可以发现
-
初始化的时候,传递了两个参数
- 函数地址 -> 对应我们
block代码块部分,被包装成一个static void __main_block_func_0(struct __main_block_impl_0 *__cself)函数- 并且都会将
block这个结构体作为第一个参数传递
- 并且都会将
- 也是一个结构体,类型是
static struct __main_block_desc_0- 这个结构体中包含两个成员,第一个是保留字段,第二个记录了当前我们定义的
block的大小
- 这个结构体中包含两个成员,第一个是保留字段,第二个记录了当前我们定义的
__main_block_impl_0的构造函数实则 3个 参数,最后一个默认值 0 - 函数地址 -> 对应我们
-
当前我声明的这个全局
block由一个__main_block_impl_0的struct来呈现, 包含两个成员-
impl,也是个结构体类型__block_impl-
FuncPtr-> 对应的就是block代码块的函数地址,构造函数中也是这么来接收的 - 通过
isa这个成员定义来看,结构有点和 OC 对象结构相仿
-
-
Desc,也是个结构体类型__main_block_desc_0
-
-
调用执行
block我是没有系统性的学习过 C++ 的,只有一些 C 基础,看资料讲解,在 C 结构体中不允许定义函数,但是 C++ 允许
转换成
C++这份代码中,执行调用block的语句是:((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);-
这里之所以能够将这个全局
block强转成__block_impl类型而直接去通过指针调用FuncPtr( 而不是block->impl->FuncPtr一层层正常思路调用 )-
结构体内存分配
-
结构体传值
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; };
impl这个成员刚好排在首位,首元素地址和是结构体地址相同 -
-
-
将
block改为有参,同样没有访问外部变量int number1 = 20; int number2 = 30; void (^block)(int, int) = ^(int a, int b){ NSLog(@"___func____: %d + %d = %d", a, b, a + b); }; block(number1, number2); -
C++代码struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_66a426_mi_0, a, b, a + b); } 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(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int number1 = 20; int number2 = 30; void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, number1, number2); } return 0; }
通过代码可见,
block和上面的结构基本一样,只不过是代码块对应的函数多了两个参数而已,并不会转加给block自己的结构体上
刚刚的源码中定义的是一个全局 block,不包含任何外部变量的访问,当我们使用外部变量时,block 又将如何处理
-
Object-C代码#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... int number1 = 20; int number2 = 30; void (^block)(void) = ^(){ NSLog(@"___func____: %d + %d = %d", number1, number2, number1 + number2); }; block(); } return 0; } -
C++代码struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int number1; int number2; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number1, int _number2, int flags=0) : number1(_number1), number2(_number2) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int number1 = __cself->number1; // bound by copy int number2 = __cself->number2; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_ec65bb_mi_0, number1, number2, number1 + number2); } 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(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int number1 = 20; int number2 = 30; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number1, number2)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
通过源码可见,两个外部变量被直接存放在
__main_block_impl_0结构体内

auto: C语言的关键字,用来修饰局部变量,其实我们声明的局部变量默认都会包含,只不过省略了,代表含义就是离开作用域就自动销毁static: 用static修饰的局部变量存放在全局数据区,会延长局部变量的寿命,超出函数的生存期
-
为什么同样是局部变量,捕获方式不同
虽然访问局部外部变量时, 默认(auto) 和static修饰后都会被捕获到block的结构体内,但是static的生命周期被延长了-
auto随着作用域释放,在将来时刻肯定不能去通过内存来访问了- 当我们用
__block修饰时,其实也是针对的auto变量,__block不能用来修饰全局或者static变量
- 当我们用
-
static局部变量一段时期内仍在内存中,在将来仍可访问,所以就使用了指针捕获
-
-
为何局部变量需要捕获,而全局变量不需要捕获
从
C++对block实现的代码层可以了解到,block定义部分被定义成一个单独的函数,访问的外部变量的真正使用也是在这个函数内,通过block自己(函数的第一个参数)访问,由于局部变量作用域的限制,所以需要被捕获,而全局可直接访问,所以不需要捕获
顺着这个思路推理,为什么
self会被block捕获?
OC 方法底层会被转成 C 函数, C 函数默认包含两个参数: self 和 _cmd, 所以 self 是作为形参提供的, 本来也是个指针,肯定是局部变量,所以会被捕获, 并且是指针捕获
当我们捕获的外界变量比较复杂时,比如是一个对象,就会涉及到内存管理,此时
__main_block_desc_0的结构也会变得复杂
会多两个函数,一个copy一个dispose,copy函数会在block被copy操作的时候自动调用,另外一个用来释放block捕获对象的计数
-
Object-C代码__block int number = 20; void (^block)(void) = ^(){ number = 30; NSLog(@"___func____: %d ", number); }; block(); -
C++代码struct __Block_byref_number_0 { void *__isa; __Block_byref_number_0 *__forwarding; int __flags; int __size; int number; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_number_0 *number; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__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_number_0 *number = __cself->number; // bound by ref (number->__forwarding->number) = 30; NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_0ef772_mi_0, (number->__forwarding->number)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->number, (void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->number, 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(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20}; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_number_0 *)&number, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
与最开始探索 __NSGlobalBlock__ 转成的 C++ 代码,又多了一个结构体 __Block_byref_number_0,也就是将我们需要捕获的目标又包装成了一层结构体(也就是包装成了一个对象), 同时与我们捕获对象时一样, __main_block_desc_0 的结构也会多出两个函数 copy 和 dispose
-
__Block_byref_number_0struct __Block_byref_number_0 { void *__isa; __Block_byref_number_0 *__forwarding; int __flags; int __size; int number; };
-
__block修饰变量后,在block内部结构中默认对其保持一份强引用 (仅适用于 ARC 下, 早期 MRC 下情况有所不同 ) -
对象类型的auto类型局部变量会根据__strong和__weak决定其内部是强引用还是弱引用
-
-
被
__block修饰的对象__attribute__((__blocks__(byref))) __Block_byref_number_0 number = { (void*)0, (__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20 };-
__forwarding指向自己
-
最终: 在
block这个对象中存在一个__Block_byref_number_0类型结构体指针number,在这个结构体中number这个成员变量记录了访问的对象
-
修改外部变量值
__Block_byref_number_0 *number = __cself->number; // bound by ref (number->__forwarding->number) = 30;
这里在取出
__Block_byref_number_0 *number;后,是通过->__forwarding->number指针来修改值的(修改的是 block 对象内存空间内的成员变量值) -
定义外部变量时
__attribute__((__blocks__(byref))) __Block_byref_number_0 number = { (void*)0, (__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20 };