上一篇我们了解了一下Block的特性,用法,概念等一些Block的基本使用方法,这一篇我们来向更深处挖掘一下,探究一下Block的实现。
Block的实质
上一篇讲到的Block的语法看上去好像很特别,但它实际上是作为极普通的C语言源代码
来处理的。通过支持Block的编译器
,含有Block语法的源代码
转换为一般C语言编译器
能够处理的源代码,并作为极为普通的C语言代码被编译
。
Clang(LLVM编译器)
具有将代码转换为我可读源代码的功能。通过“-rewrite-objc
”选项就能将含有Block语法
的源代码变换为C++的源代码
。说是C++,其实也仅是使用了struct
结构,其本质是C语言
源代码。
clang -rewrite-objc 源代码文件名
下面我们转换Block语法。
int main() {
void (^block)(void) = ^{
printf("Block\n");
};
block();
return 0;
}
这是一个最简单的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("Block\n");
}
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() {
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;
}
乍一看完全没有看下去的想法对不对,8行的源代码增加到了四十多行,我们将其拆解分成几个部分理解,多看几次相信就会理解其中原理了,首先我们来看最初源代码中的Block语法。
^{
printf("Block\n");
};
我们再看它变换后的源代码。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
Block被作为最简单的C语言函数
来处理,根据语法所属的函数名(也就是main)
和该Block语法在该函数出现的顺序值(此处为0)
来给经clang
变换的函数命名。
该函数的参数_cself
相当于C++实例方法
中指向实例自身
的变量this
,或是Objective-C实例方法
中指向对象自身的变量self
,即参数_cself
为指向Block值
的变量。
struct __main_block_impl_0 *__cself
之前说C++
中的this
与Objective-C
的self
相同,即参数_cself
是__main_block_impl_0
结构体的指针。
我们再来看看该结构体的声明:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
我们将其构造函数剔除,__main_block_impl_0
结构体看起来是不是非常简单。第一个成员变量是impl
,我们再来看一下其__block_impl
结构体的声明。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
看了这个结构体的成员变量isa,Flags标志,版本升级所需的区域,函数指针
,这些我们会在后面详细说明
,我们再来看一下__main_block_impl_0
结构体的第二个成员变量Desc
,以下为其struct __main_block_desc_0
结构体声明。
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
从其中的成员名称所示,其结构为今后版本升级所需的区域
和Block的大小
。
以上就是__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;
}
该构造函数
使用了之前我们之前说明的__block_impl
结构体以及struct __main_block_desc_0
结构体来初始化__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_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *block = &tmp;
这样就容易理解了。该源代码将__main_block_impl_0
结构体类型的自动变量tmp
,即__main_block_impl_0结构体实例
的指针
,赋值给__main_block_impl_0
结构体指针类型的变量block
。
以下这部分代码对应最初源代码。
void (^block)(void) = ^{
printf("Block\n");
};
将Block语法生成的Block赋值给Block类型变量block
。它等同于将__main_block_impl_0结构体实例
的指针
赋值给变量block
。该源代码中的Block就是__main_block_impl_0结构体类型
的自动变量,即__main_block_impl_0结构体实例
。
下面再来看看__main_block_impl_0
结构体实例构造参数
。
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
__main_block_func_0
是由Block语法转换的C语言函数指针
。__main_block_desc_0_DATA
是作为静态全局变量
初始化的__main_block_desc_0结构体实例指针
。以下为__main_block_desc_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_impl_0结构体实例
的大小进行初始化。
好了上面我们将整个__main_block_impl_0结构体
的内容拆开分解研究了一遍,现在我们将他们组合起来看看,这个__main_block_impl_0
是究竟如何进行的初始化。我们将__main_block_impl_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;
__main_block_func_0
函数指针被赋值给成员变量FuncPtr
,我们再看看使用Block的部分
block();
此部分经过转换的源代码如下:
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
我们再将其简化一下,简化后如下:
(*block->impl.FuncPtr)(block);
这就是最简单的使用函数指针调用函数,由Block语法转换的__main_block_func_0函数的指针
被赋值在成员变量FuncPtr
中,这也说明了__main_block_func_0函数
的参数__cself
指向Block的值,在调用该函数的源代码中可以看出Block正是作为参数
进行了传递。
到这里我们总算摸清楚了Block实现的原理,现在我们再来说说上文跳过的__block_impl
结构体,这个结构体的第一个成员变量叫做isa
,是不是似曾相识,Objective-C
中对象的实质是不是就是一个objc_object
的结构体,在Objective-C
中由类生成对象,而类本身也是一个对象。
objc_object
的结构体中也有一个成员变量叫做isa
,而通过这个isa
我们可以访问生成objc_object
的类objc_class
,而objc_class
中也有一个isa
用于访问其元类
。这部分内容大家可以去看我的另一篇简书文章iOS中的class,SEL,IMP,由此我们便可以知道所谓Block就是Objective-C
对象。
__main_block_impl_0
结构体就相当于基于objc_object
结构体的Objective-C
类对象的结构体,__block_impl
便相当于objc_class
,__main_block_impl_0
初始化时的isa = &_NSConcreteStackBlock
,即_NSConcreteStackBlock
相当于objc_class
的实例,将Block当作对象处理时,关于该类的信息就放置于_NSConcreteStackBlock
中。
以上就是Block的实质,理解起来可能稍微有些复杂,一定要将整体的源码拆解开来分部理解,多看几次,并且理解iOS中类与对象的实质,这对于理解Block也很有帮助。