分析 Block 的实现方式

2018-02-27 11:25:52来源:http://liuduo.me/2018/02/19/block-impl/作者:刘铎.Me人点击

分享

clang 是 Objective-C 的编译器前端,用它可以将 Objective-C 代码转换为 C/C++ 代码,然后可以来分析 Objective-C 中的一些特性是怎么实现的。


首先创建一个命令行应用程序,在 main.m 中,修改 main 函数的内容:


int main() {
void (^task)(void) = ^{
};
task();
return 0;
}

把上面代码用:


clang -rewrite-objc main.m

可以在 main.cpp 中找到生成后的代码,可以看到生成后的 main 函数是这样的:


int main() {
void (*task)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)task)->FuncPtr)((__block_impl *)task);
return 0;
}
Block 的内存布局和创建过程

先看第一行:


void (*task)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

= 号左边是声明了一个名为 task 的函数指针。



= 号右边创建了一个__main_block_impl_0
类型的结构体,并通过__main_block_impl_0
的初始化函数来初始化,简写如下:


_main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)


第一个参数是__main_block_func_0
指针,第二个参数取了 __main_block_desc_0_DATA 的地址。



现在再看看__main_block_func_0
是什么:



生成的代码中__main_block_func_0
的定义如下,是一个静态全局函数:


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}


可以看到它有一个名为cself 的参数,是一个
main_block_impl_0 结构体类型的指针。


于是我们继续来看 __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;
}
};

它拥有两个成员变量,impl 和 Desc,还带有一个初始化函数。impl 的类型为 __block_impl 结构体,可以找到它的定义如下:


struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};


__block_impt 就是 Block背后的数据结构


isa 字段代表 block 对象是由哪一个类实例化来的。
Flags 标识符。
Reserved 是为未来保留的字段。
FuncPtr 是一个函数指针。


再看看main_block_impl_0 结构体的另一个成员变量 Desc,是一个
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_desc_0 有两个字段,一个保留字段 reserved,和一个保存 Block 占用内存大小的字段 Block_size。并且同时创建了一个main_block_desc_0_DATA 的变量,初始化为{ 0, sizeof(struct __main_block_impl_0) }



通过__main_block_impl_0
的定义可知,__main_block_impl_0
的初始化方法的第一个参数是一个函数指针。再回到 main 函数中,在初始化 _main_block_impl_0 时,可以看到传入的函数指针为__main_block_func_0
,因此 Block 在调用的时候就是调用的__main_block_func_0
这个函数。__main_block_func_0
函数接收一个参数__cself
,也是 __main_block_impl_0 结构体类型的指针,它代表这个 Block 自身。



main_block_impl_0 初始化函数的第二个参数则传入了main_block_desc_0_DATA 变量,它的 reserved 变量为 0,Block_size 为 __main_block_impl_0 的大小。


再回到:


void (*task)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));


可以看到创建好的__main_block_impl_0
结构体,通过 & 操作符获取了它的地址,然后转换为 void (*)() 这个函数指针类型,最后赋值给 task 这个函数指针。


通过分析 main 函数的第一行代码,大概知道了 Block 在内存中的布局是什么样的,也知道了 Block 的创建过程,现在来看看 Block 是怎么被调用的。


Block 的调用过程

现在开始研究 main 函数的第二行代码:


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

由于代码中涉及到很多类型强转,看上去比较乱,把它分解如下:


__block_impl *taskImpl = (__block_impl *)task;
void (*func)(__block_impl *) = (void (*)(__block_impl *))(taskImpl->FuncPtr);
func(taskImpl);


可见 Block 的调用就是通过__block_impl
中的函数指针 FuncPtr 来调用函数。Block 的调用就是函数的调用。



通过上面的分析可知,FuncPtr 指向的函数为__main_block_func_0
,上面的例子中__main_block_func_0
函数的内容为空,现在给 Block 中添加一句代码:


int main() {
void (^task)(void) = ^{
printf("Hello");
};
task();
return 0;
}


通过clang -rewrite-objc main.m
重新生成后,会发现 __main_block_func_0 函数的内容就是 Block 的内容:


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello");
}

所以 Block 的调用就是函数的调用。


Block 截获自动变量

现在来看看 Block 截获自动变量是怎么实现的,把 OC main 函数中的代码改为:


int main() {
int i = 10;
void (^task)(void) = ^{
printf("%d", i);
};
task();
return 0;
}


查看生成的代码,会发现__main_block_impl_0
中增加了一个成员变量 i:


struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i; // 增加了一个成员变量 i
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};


在__main_block_impl_0
也增加了 _i 参数用于初始化 i 变量。这里的 i 的初始化时值传递的,因此 i 是保存了 Block 外面的变量 i 的值的副本。


再看看 Block 截获对象的情况,把 main 函数改为:


int main() {
NSObject *object = [[NSObject alloc] init];
void (^task)(void) = ^{
NSLog(@"%@", object);
};
task();
return 0;
}


生成的代码中的__main_block_impl_0
同样是增加了一个成员变量。


struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *object; //
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_object, int flags=0) : object(_object) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

由于这里 _object 也是一个 Block 外 object 对象的指针的复制值,同样无法用 Block 中保存的 object 来修改 Block 外的 object 的值。


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台