我们项目中经常使用 block 来进行回调传值,之前我对 block 的认识也就仅仅的停留在基础的层面,包括简单的使用和一些基本的避免循环引用的方法,这篇博客是我在对 block 进行了更深一层的学习之后的记录和总结,希望对大家有所帮助。
Block 的本质
新建一个命令行项目,写一个简单的 block 如下面所示。
void (^myBlock)(void) = ^{
NSLog(@”11″);
};
myBlock();
使用 clang 工具把 main.m 文件编译为 cpp 文件。main 函数变成了下面这个样子
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {__AtAutoreleasePool __autoreleasepool;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
里面有很多类型转换的代码,但是不难看出,我们的 block 被编译成了一个__main_block_impl_0 类型的变量,我们可以搜索一下,这是一个结构体。
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;
}
};
这个__main_block_impl_0 结构体有两个属性,一个是__block_impl 类型的结构体,一个是指向__main_block_desc_0 结构体的指针。我们一个一个的看。
//__block_impl 结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
我们可以看到这个结构体有一个 isa 指针,同时在__main_block_impl_0 这个结构体中是直接包含__block_impl 结构体,从内存结构中其实这个 isa 指针就在__main_block_impl_0 里面,也就是说 block 被编译成了一个拥有 isa 指针的结构体。了解过 isa 的应该知道,这算是 oc 对象的一个标志了,那也就是说 block 其实也是一个 oc 的对象。
我们继续看__main_block_impl_0 内部
__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;
}
这是一个初始化的方法,因为这是 c ++ 代码嘛,c 语言的结构体应该是不可以这样写的。
只要是给这个__block_impl 类型的 impl 赋值,这里我们先主要看这个 fp 指针。
从编译之后 main 函数中看出来,在初始化__main_block_impl_0 的时候传进去的参数是一个__main_block_func_0。
// __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_pr_dx33d329d7xxgk9kgjdw80000gp_T_main_85af40_mi_0);
}
可以看出来这是我们 block 回调执行的方法,也就是说 impl.FuncPtr 指向了 block 的回调。
__main_block_impl_0 还有一个 Desc 参数,我们可以在文件中查一下。
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)};
reserved 是一个保留字段,Block_size 就是记录我们这个 block 占内存的大小。
本节小结 Block 本质就是一个 OC 对象,内部有 isa 指针。Block 是封装了函数调用和函数调用的环境的 OC 对象。
我们来个草图来梳理一下最简单的 block 的内部结构。