iOS Block的底层原理
block的实质
说简单点,block就是匿名函数,和C语言的函数相比就有两点不同
- 没有函数名
- 带有“^”
但是block本质究竟是个什么鬼???
下面我们写一个简单的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
printf("I am a block");
};
block();
}
return 0;
}
幸运的是,clang为我们提供了把代码转化为可读源代码的功能,我们可以通过“-rewrite-objc”选项就能把我们的代码转化为C++代码,虽然转化后的文件是.cpp,其实本质就是C语言。
clang -rewrite-objc 源代码
这里简单说一下clang,clang可以带很多参数,比如:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源代码 -o 目标文件
假如有链接库的话还可以加-framework 等等,具体的大家clang -help就可以了
我们写的这个block可以说是最简单的一个block了,它没有参数没有返回值。
该源代码通过clang变换为以下形式:
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) {
printf("I am a block");
}
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;
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;
}
看到以上的代码,相信很多人表情是黑人问号脸,不过不用紧张,这段代码虽然相对于我们的源码变长了很多,我们只要静下心来仔细分析下,其实很简单,首先我们先看block的实现
^{
printf("I am a block");
};
变换后的源代码为
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("I am a block");
}
这是一个典型的C语言函数了,函数名为__main_block_func_0, 参数为__main_block_impl_0结构体指针,此处函数名的生成是根据block语法所属的函数名(我们这里是写在main函数中了)和该Block语法在该函数出现的顺序(此处是0),函数的参数__cself相当于C++或java中的this,相当于OC中的self,其实__cself即为指向block值的变量,不过我们这里block语法中就一个简单的打印语句,用不到__cself,后面我会介绍使用__cself的例子。
我们再来分析一下__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;
}
};
因为这段代码里包含了构造函数,看起来还是比较复杂,我们可以先除去构造函数,这样就会变得非常简单,如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
这样就看起来清爽多了,这个结构体就包含两个成员变量,第一个是impl,类型是
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
第二个成员变量是Desc指针,类型是
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
OK,现在我们再来看看struct __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;
}
现在我们把__main_block_impl_0拆分开分析完了,以上就是初始化结构体成员的源代码,我们先不看每个成员什么意思,我们先来看看该构造函数的调用
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
因为强转太多,我们把强转的部分去掉,如下:
struct __main_block_impl_0 tmp = __main_block_impl1_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 * block = &tmp;
这样就很容易理解了,就是一个简单的结构体指针赋值,即把栈上生成的__main_block_impl1_0结构体实例的指针,赋值给__main_block_impl1_0结构题指针类型的变量,上面这部分代码就是对应源码中的
void (^block)(void) = ^{
printf("I am a block");
};
将Block语法生成的Block赋给Block类型变量block,它等同于将__main_block_impl_0结构体实例的指针赋给block,我们来看下__main_block_impl_0结构体实例的构造参数
__main_block_impl1_0(__main_block_func_0,&__main_block_desc_0_DATA);
第一个参数是由Block语法转换的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0的结构体实例指针,__main_block_desc_0的初始化部分如下:
static struct __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
由此可知,该源代码使用Block,即__main_block_impl_0结构体实例的大小进行初始化,我们再看看__main_block_impl1_0结构体实例是怎么根据这些参数进行初始化的,我们展开__main_block_impl1_0结构体中的__block_impl结构体,可记述为如下形式:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 * Desc;
};
该结构体构造函数会这样初始化
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
我们再来看一下使用block的部分
block();
这部分可变换为以下源码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
去掉转换部分
(*block->impl.FuncPtr)(block);
这就是简单的使用函数指针调用函数了,由Block语法转换的main_block_func_0函数的指针被赋值给成员变量FuncPtr中,到此我们总算是摸清了block的实质,调用时就是一个简单的函数调用,当然,我们只是说了关于block的调用过程,关于block的问题还有很多,如循环引用,截获自动变量值等,后面的文章我会详细讲解。