2019-11-27-从使用到了解block底层原理(二)-2019

iOS技术成长之路

Posted by DM on November 27, 2019

上篇文章中主要通过简单的 demo 展示了 block 的使用场景,本篇将基于上篇文章iOS 技术篇:从使用到了解 block 底层原理 (一)进一步了解 block 底层的实现原理。

block 作为一种”带有自动变量值的匿名函数”,在实际编译时,我们无法转换成我们能够理解的源代码,但 clang(LLVM 编译器)具有转化为我们可读源代码的功能。终端输入如下命令行,可获取.cpp 文件。

clang -rewrite-objc 源代码文件名

在 main.m 中实现如下

int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^myBlock)(void)=^{
NSLog(@"testtest");
};
myBlock();
}
return 0;
}

通过 clang 我们获取到 main.cpp 文件,打开文件我们可以看到大约 10w 行代码,当然我们不用从头到尾细细分析每块的功能。我们只需要看最后几行然后步步分析就可以。

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;
}

在 main 函数入口这里我们可以看到我们原实现 block 被转化为了另一种方式展示。不要着急看不到,从这里开始一步步分析。

void(*myBlock)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

通过全局搜索可以看到__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;
}
};

这里包含了两个结构体block_impl 和main_block_desc_0,

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

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_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;
}

不难看出: 1:main_block_impl_0 就是 block 的一个 C++的实现,_0 代表是 main 中的第几个 block,里面包含了保留值 reserved 和大小 Block_size。 2:block_impl 就是 block 的内部实现。其中包含了 isa 指针、flags 标示、reserved 保留值和 funcPtr 定义类型 (ps:_NSConcreteStackBlock 相当于 class_t 结构体实例 想知道可以自己了解下)。

从这里我们可以大概看出main_block_impl_0 就是传了两个值main_block_func_0 和&main_block_desc_0_DATA。一个是 func 结构体,另一个是 desc 指针。 以上就是初始化main_block_impl_0 结构体成员的源代码。 接下来,将我们原来看到不太容易理解的__main_block_impl_0 通过以上分析可以将

void(*myBlock)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

转化为:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0*myBlock = &tmp;

这样就容易理解了,该源代码将main_block_impl_0 结构体类型的自动变量,即栈上生成的main_block_impl_0 结构体实例的指针,赋值给__main_block_impl_0 结构体指针类型的变量 myBlock。 而
myBlock();的底层实现如下:

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

转化之后就是

(*myBlock->impl.FuncPtr)(myBlock);

这就是简单的函数指针调用函数。可以理解为:有 block 语法转化的__main_block_func_0 函数的指针被赋值成员变量 FuncPtr 中。 到此也就了解了 block 的实质。