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_0
struct __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 };