从使用到了解block底层原理(二)

技术成长之路

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。 而


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

转化之后就是

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