探索 Block (Block 实现原理)
Posted chenxianming
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探索 Block (Block 实现原理)相关的知识,希望对你有一定的参考价值。
前言
要探索Block前先说一下我对Block的理解,我把它理解为:能够捕获它所在函数内部的变量的函数指针、匿名函数或者闭包。注意红色部份说的是它的精髓所在。希望看我这篇文章的人能够跟我说的步骤去做,做起来也比较简单,这样会有更好的效果,当然如果只看文章就能够让读者明白,那是我更加希望的。
一、首先,我们准备一个.m文件。我这里是main.m。内容如下:
int main(int argc, char * argv[]) { void (^test)() = ^(){ }; test(); }
接下来我要用到一个命令clang src.m -rewrite-objc -o dest.cpp.这个意思是用clang编译器对源文件src.m中的objective-c代码转换成C代码放在dest.cpp文件。其实xode编译时也会帮我们转换。我们这样就可以dest.cpp在看到我们定义和调用的block转换成C是怎么样的。执行命令后查看这个dest.cppw会发现有一大堆代码。下面我把对我们有用并能够说清楚原理的关键贴上来并加以注释:
//__block_imp: 这个是编译器给我们生成的结构体,每一个block都会用到这个结构体
struct __block_impl { void *isa; //对于本文可以忽略 int Flags; //对于本文可以忽略 int Reserved; //对于本文可以忽略 void *FuncPtr; //函数指针,这个会指向编译器给我们生成的下面的静态函数__main_block_func_0 };
/*__main_block_impl_0: 是编译器给我们在main函数中定义的block
void (^test)() = ^(){
};
生成的对应的结构体
*/
struct __main_block_impl_0 {
struct __block_impl impl; //__block_impl 变量impl struct __main_block_desc_0* Desc; //__main_block_desc_0 指针,指向编译器给我们生成的结构变量__main_block_desc_0_DATA __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { //结构体的构造函数 impl.isa = &_NSConcreteStackBlock; //说明block是栈block impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;}};
//__main_block_func_0: 编译器根据block代码生成的全局态函数,会被赋值给impl.FuncPtr static void __main_block_func_0(struct __main_block_impl_0 *__cself) { }
//__main_block_desc_0: 编译器根据block代码生成的block描述,主要是记录下__main_block_impl_0结构体大小
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)}; //这里就生成了__main_block_desc_0的变量__main_block_desc_0_DATA
//这里就是main函数了 int main(int argc, char * argv[]) { void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //下面单独讲 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test); //下面单独讲 }
what the hell is that!!!! 没错,这也是我一开始看到这堆东西的感受。因为很多人讲Block原理都贴这个而且没有注释或很少注释,我也百度出来看了好几个。接下来就要说明白这堆代码。一定要有耐心,首先,对着上面代码注释过几遍main函数前系统给我们生成的这些结构体函数之间的关系,如果一次能明白自然是好,过了几遍都没明白也没关系。
回归一开始我对block的理解,先忽略它能够捕获所在函数内部的变量,那么它就是一个函数指。
void (^test)() = ^(){ }; 就对应着 void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
这个总的来说就是定义一个函数指针指向一个地址,但是这个地址并不是我样平常的函数的入口地址
转换后代码的要一段段从后往前组合分析:
__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))就是创建了一个__main_block_impl_0结构体的一个实例 &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))取这个实例的地址 ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))把实例地址强转为一个函数地址 void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
那么这整句就是说定义一个函数指针指向一个新创建的__main_block_impl_0实例的地址。注意创建这个实例时构选函数传的两个参数,
正是编译器帮我们生成的静态函数__main_block_func_0及__main_block_desc_0的变量__main_block_desc_0_DATA
test(); 对应着 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
总的来说意思就是通过函数指针test调用函数FnucPtr,传的参数为指针test本身。
虽然能够理解这句的意思,但是按照上面分析test的赋值方法
这里有两个疑问:
1、调用时不是应该这样调才对吗 test(test它指向__main_block_impl_0)->impl.FuncPtr,难道它跟((__block_impl *)test)->FuncPtr)是同等作用?
2、FuncPtr(即__main_block_func_0)的参数类型不是__main_block_impl_0 *,为什么clang编译出来后是__block_impl*。其实这里不管类型是什么,它还是传了test作为参数进去,所是不会有错的。
好了讲到这里,就可以进行一个中途简单性的总结:忽略中间的复杂分支,留下主线,当我们声明一个block变量a并为它赋值时,其实就是创建一个函数指针ptrA,再根据给block a赋值的代码生成一个静态函数,而指针ptrA就指向这个静态函数。block a调用时就是使用函数指ptrA调用生成的静态函数。
讲到这里第一部分就结束了,接下来进行第二部分。
二、
以上是关于探索 Block (Block 实现原理)的主要内容,如果未能解决你的问题,请参考以下文章
iOS底层探索之Block——初识Block(你知道几种Block呢?)