本篇文章主要是使用 clang 重写 dispatch_block_t 类型对象.
最基础的 Block
- (void)test {
dispatch_block_t block = ^ {
};
block();
}
这个语法块是一个最基础的 Block, 使用:
clang -rewrite-objc fileName.m
命令重写所在文件, 可以得出 Block 相关的 C++ 版本实现函数:
//重写后的 block
struct __TestObject__test_block_impl_0 {
struct __block_impl impl; //block 的基础结构
struct __TestObject__test_block_desc_0* Desc; //block 的描述
__TestObject__test_block_impl_0(void *fp, struct __TestObject__test_block_desc_0 *desc, int flags=0) { //构造函数
impl.isa = &_NSConcreteStackBlock; //语法块类型
impl.Flags = flags; //标记位
impl.FuncPtr = fp; //函数指针, FuncPtr 是 void * 型指针
Desc = desc; //描述
}
};
//block 执行部分被重写为一个函数
static void __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
//block 执行函数内部自带隐形参数 __cself
int i = 0;
}
//用来描述 block 的结构体
static struct __TestObject__test_block_desc_0 {
size_t reserved; //reserved 的原意是保留
size_t Block_size; //block 占用内存大小
} __TestObject__test_block_desc_0_DATA = { 0, sizeof(struct __TestObject__test_block_impl_0)}; //构造函数
//重写后的 test 函数:
static void _I_TestObject_test(TestObject * self, SEL _cmd) {
dispatch_block_t block = ((void (*)())&__TestObject__test_block_impl_0((void *)__TestObject__test_block_func_0, &__TestObject__test_block_desc_0_DATA)); //创建一个 block 并进行初始化
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); //调用 block, 将 block 本身作为参数传入
}
一个最简单的 block 被重写后会分为 block 结构体本身, block 代码段函数, block 描述结构体三个部分, 这就是 block 的实质.
有返回值的 block
- (void)test {
int (^block)(void) = ^ {
return 1;
};
block();
}
重写后
//block 执行函数有 int 型返回值
static int __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
return 1;
}
static void _I_TestObject_test(TestObject * self, SEL _cmd) {
int (*block)(void) = (...((void *)__TestObject__test_block_func_0, ...)); //将 block 执行函数的函数指针转换成 void * 型
((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); //调用前需要转换回原类型
}
基本上不同的地方只有 block 的执行函数变成了有返回值的函数, 但依旧会转换成 void * 型指针来存储, 在调用之前再将指针类型转换回来, 然后再进行调用.
使用外部变量的 block
我们知道 block 里面可以使用局部变量、成员变量以及全局变量等外部变量, 那使用外部变量的 block 重写后会变成什么样子.
- (void)test {
NSObject * obj = [[NSObject alloc] init];
dispatch_block_t block = ^ {
obj;
};
block();
}
重写后去掉刚刚提到过的部分:
struct __TestObject__test_block_impl_0 {
...
NSObject *obj; //原 block 结构体会增加一个同名同类型变量
__TestObject__test_block_impl_0(..., NSObject *_obj, ...) : obj(_obj) { //构造函数会自动将参数 _i 赋值给成员 i
...
}
}
static void __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; //函数内部会通过隐藏参数 __cself 找到 block 结构体中的成员 obj
obj;
}
//出现了两个函数, 一个名为 copy, 一个名为 dispose
static void __TestObject__test_block_copy_0(struct __TestObject__test_block_impl_0*dst, struct __TestObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __TestObject__test_block_dispose_0(struct __TestObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->i, 3/*BLOCK_FIELD_IS_OBJECT*/);}
//新的函数被包含进 block 的描述结构体里面
static struct __TestObject__test_block_desc_0 {
...
void (*copy)(struct __TestObject__test_block_impl_0*, struct __TestObject__test_block_impl_0*);
void (*dispose)(struct __TestObject__test_block_impl_0*);
} __TestObject__test_block_desc_0_DATA = { ..., __TestObject__test_block_copy_0, __TestObject__test_block_dispose_0}; //使用构造函数构造两个新的函数
static void _I_TestObject_test(TestObject * self, SEL _cmd) {
NSObject * obj = ...;
dispatch_block_t block = (..., obj, ...); //创建时将 obj 传入构造函数
....
}
可以看到, 使用到局部变量 obj 的 block 只是在内部多了一个一模一样的变量. 同时多了 copy 和 dispose 方法,
这两个函数是给 block 内的 obj 赋值用的, 本例中的 obj 的引用计数会 + 1, 所以如果是 self.block 内部使用 self 的话, 就会因为 copy 函数的原因, 导致循环引用. 使用局部变量和成员变量是这种情况.
我们看到, 这里的 obj 是直接传递给构造函数的, 如果使用的不是 OC 对象, 而是基础数据类型 int i 的话, 这里传入的就是 C 语言中常说的形参, 所以 block 内部对基础数据类型的操作都无法还原回原本的变量上面.
为局部变量添加 __block 标记
已经将 block 运用自如的朋友们应该对 __block 标记并不陌生, 这个标记用于解决刚才提到的, 语法块内部无法修改外部局部变量的问题. 那 __block 究竟是如何工作的, 我们继续重写一下看看.
- (void)test {
__block int i = 0; //使用 __block 来标记局部变量 i
dispatch_block_t block = ^ {
i = 1; //这里允许更改
};
block();
}
重写后:
//出现了一个新的结构体
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; //本类型指针
int __flags;
int __size;
int i;
};
struct __TestObject__test_block_impl_0 {
...
__Block_byref_i_0 *i; //i 变成了一个 __Block_byref_i_0 * 类型的结构体指针
__TestObject__test_block_impl_0(..., __Block_byref_i_0 *_i, ...) : i(_i->__forwarding) { //构造函数将参数 _i->__forwarding 赋值给 i
...
}
}
static void __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; //函数内部也开始使用新的结构体指针 i
(i->__forwarding->i) = 1; //给 i 赋值
}
static void _I_TestObject_test(TestObject * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0}; //使用 __Block_byref_i_0 结构体将局部变量 i 封装起来, __forwarding 赋值为自身的地址
dispatch_block_t block = (..., (__Block_byref_i_0 *)&i, 570425344); //创建时将 i 的地址传入构造函数, 并且标记位传入了新的值
....
}
可以看到 __block 标记相比于之前直接使用外部变量的方式, 独特了很多. 支持 __block 标记的内容主要是结构体 __Block_byref_i_0. 依靠的方式就是将被 __block 标记的局部变量, 用一个新的结构体封装好, 之后把构造好的结构体的地址传入 block 的构造函数, 并在应用中使用对应的结构体指针.
到这里简单的将 block 的几种形式重写了一下, 看了一下大致的实现思想, 下一篇文章将深入 block 的源码, 详细分析 block 的实现原理.
网友评论