苹果的官方文档将Block描述为具有C语言级的语法,同时有运行时特性的对象,参见Blocks Programming Topics Introduction。为什么说Block是一个对象呢,我们可以从Block的内部实现中得到一定的启示。
为了分析Block的内部实现,我们需要将Block语法转化为我们可读的源代码,我们可以使用clang命令的-rewrite-objc
选项将Block语法转化为C++代码。
- clang是基于LLVM的C语言族编译器,可以编译C、C++、Objective-C/C++等语言。
- 命令是
clang -rewrite-objc ${source_file}.m
,会生成同名的.cpp文件。
我们先从最简单都Block开始:
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
使用clang -rewrite-objc
转化后的关键代码如下:
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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
因为C++中struct和class有相似的语义,所以转化后的代码struct我们可以全部认为是class。
我们首先看main函数,第一行代码对应着源代码中的第三行,我们再做一些精简:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
这里首先调用了__main_block_impl_0
的构造函数,然后将生成的对象指针传递给了blk变量。
__main_block_impl_0
包含了两部分数据:__block_impl
是Block的实现部分,__main_block_desc_0
是Block的描述。
从__main_block_impl_0
构造函数中我们可以看到,它第一个参数是一个void类型的指针,void类型的指针类似于Objective-C中的id,理论上可以传递任何数据。它的第二个参数是一个__main_block_desc_0
类型的结构体,我们可以找到这个结构体的定义,大概可以得出这个结构体是用来描述这个block的大小的。
我们回到main函数的第一行,这里调用__main_block_impl_0
的构造函数时提供的第一个参数是一个名为__main_block_func_0
的静态函数指针,而__main_block_func_0
的实现正是源代码中Block体的实现。
__main_block_impl_0
构造函数的第二个参数是一个名为__main_block_desc_0_DATA
的变量,我们可以看到这个变量刚好是描述这个Block对象大小的。
我们接着看main函数,同样我们做一些精简:
__block_impl *blkTmp = (__block_impl *)blk;
blkTmp->FuncPtr(blk);
这里就是一个简单的通过函数指针调用函数,从前面分析得知,被调用的就是__main_block_func_0
函数。
这个例子中,我们跳过了两个重要的信息,一个是__block_impl
的isa变量,在这个例子中,isa变量被赋值为&_NSConcreteStackBlock,在GNUStep开源代码中,我们可以找到这个变量的定义:
struct objc_class _NSConcreteGlobalBlock;
struct objc_class _NSConcreteStackBlock;
struct objc_class _NSConcreteMallocBlock;
我们知道每一个Objective-C的对象,在内部实现中都有一个isa指针,指向一个struct objc_class
的结构表明这个对象是什么类。所以我们可以得到类似的结论:上面的Block是一个_NSConcreteStackBlock
对象。
关于
_NSConcreteGlobalBlock
和_NSConcreteMallocBlock
对象我们后面再详细解释。
另外一个重要的信息是__main_block_func_0
的参数中有一个__cself
(我们可以理解为self),这个变量在这个函数中虽然没有使用到,但是这个变量有重要作用,通过它我们可以访问到Block内部的任何变量,我们将在后续的例子中说明。