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 {
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
int main() {
void (*block)(void) = (void (*) (void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*) (struct __block_impl *))((struct __block_impl *)block)->FuncPtr)((struct __block_impl *)block);
return 0;
}
8行的代码竟然增加到了43行。但是如果仔细观察就能发现,这段源码虽长却不那么复杂。下面,我们将源码分成几部分逐步理解。首先看最初的Block语法。
^{
printf("Block\n");
};
可以看到,变换后的源码中也含有相同的表达式
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
如变换后的代码所示,通过Blocks使用的匿名函数实际上被作为简单的C语言函数来处理。另外,根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。
该函数的参数_cself相当于C++实例方法中指向实例自身的变量this,或是OC中指向对象自身的变量self,及参数__cself为指向Block值的变量。
遗憾的是,由这次Block语法变换而来的__main_block_func_0函数并不使用__cself。
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。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
我们从其名称可以联想到某些标志、今后版本升级所需的区域以及函数指针。这些会在后面详细说明。第二个成员变量时Desc指针,一下为其结构体的声明:
struct __main_block_desc_0 {
unsigned long reserved;
unsigned long 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;
}
以上就是初始化__main_block_impl_0结构体成员的源代码。
_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员。
void (block)(void) = (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结构体类型的自动变量,即栈上生成的__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);
第一参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。以下为__main_block_desc_0结构体实例的初始化部分代码。
static struct __main_block_desc_0 __main_block_desc_0_DATA {
0,
sizeof(struct __main_block_impl_0)
}
由此可知,该源代码使用Block,即__main_block_impl_0结构体实例的大小,进行初始化。
下面看看栈上__main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。如果展开__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();
这部分可变换为一下源码:
((void (*) (struct __block_impl *)) ((struct __block_impl *)block)->FuncPtr)((struct __block_impl *)block);
去掉转换部分
(*block->impl.FuncPtr)(block);
这就是简单地使用函数指针调用函数。正如刚才确认的,由Block语法转换的__main_block_func_0函数指针被赋值成员变量FuncPtr中。另外也说明了,__main_block_func_0函数的参数_cself指向Block值。在调用该函数的源代码中可以看到Block正是作为参数进行了传递。
_NSCreteStackBlock到底是什么?
isa = &_NSConcreteStackBlock;
将Block赋值给Block的结构体成员变量isa。为了理解它,首先要理解Objective-C类和对象的实质。其实,所谓Block就是Objective-C对象。
“id”这一变量类型用于存储Objective-C对象。在Objective-C源代码中,虽然可以像使用void *类型那样随意使用id,但此id类型也能够在C语言中声明。在/usr/include/objc/runtime.h中是如下进行声明的:
typedef struct objc_object {
Class isa;
} *id;
id为objc_object结构体指针类型。我们再来看看Class。
typedef struct objc_class *Class;
Class为objc_class结构体的指针类型。objc_class结构体在/usr/include/objc/runtime.h中声明如下:
struct objc_class {
Class isa;
};
这与objc_object结构体相同。然而,objc_object结构体和objc_class结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。下面我们通过编写简单的Objective-C类声明来确认一下。
@interface MyObject: NSObject {
int val0;
int val1;
}
@end
基于objc_object结构体,该类的对象的结构体如下:
struct MyObject {
Class isa;
int val0;
int val1;
};
MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。“Objective-C中由类生成对象”意味着,像该结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。
各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下:
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
}
在Object中,比如NSObject的class_t结构体实例以及NSMutableArray的class_t结构体实例等,均生成并保持各个类的class_t结构体实例。该实例持有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类的指针,并被Objective-C运行时库所使用。
在Block结构体此__main_block_impl_0结构体相当于基于objc_object结构体的Objective-C类对象的结构体。另外,对其中的成员变量isa进行初始化,具体如下:
isa = &_NSConcreteStackBlock;
即_NSConcreteStackBlock相当于class_t结构体实例。在将Block作为Objective-C的对象理解时,关于该类的信息放置于_NSConcreteStackBlock中。
所以说,Block的实质,即为Objective-C对象。