Blocks的实现
Block的实质
通过-rewrite-objc
将Block语法转为C++源代码
//源代码
void(^blk)(void) = ^{
printf("Block\n")
}
block是"带有自动变量值的匿名函数",
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block\n")
}可以看到其执行内容会按照普通的C函数处理,并且Block会根据所属的函数名(main函数)和该block出现的顺序值(为0)进行命名。并且其函数的执行参数即为
__cself
也就是指向该block的机构体的指针Block其实是
Object-C
对象block转换为结构体后其结构如下:
struct __main_block_impl_0 { struct __block_impl impl;
struct __main_block_desc_0 *Desc;
//结构体初始化函数
__main_block_impl_0(void *fp, strct __main_block_desc_0, int flags=0) {
impl.isa = & NSConcreatStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FunctionPtr;
}
struct __main_block_desc_0 {
unsigned long reserved; //今后版本升级所需的区域
unsigned long Block_size; //block的大小
}因此将block结构体展开的话,其初始化结构如下:
static struct __main_block_desc_0 __main_block_desc_0_DATA = { 0,
sizeof(struct __main_block_impl_0) //block大小
}
//初始化
isa = &_NSConcreatStackBlock;
Flags=0;
Reserved=0;
FuncPtr=__main_block_func_0;
Desc= &__main_block_desc_0_DATA;当执行该block时,即执行block中的FuncPtr函数,然后将block结构体指针作为参数传入:
((void(*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct _block_impl*)blk)
注意
对于
isa=&_NSConcreatStackBlock;
因为block也是一个对象,因此与其它OC对象类型,其isa指针指向其类结构体,即block的类信息就放在_NSConcreatStackBlock
中
截获自动变量
所谓的"截获自动变量值"其实是在执行block时,Block语法表达式所使用的自动变量被保存到Block的结构体实例中
截获了自动变量的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
const char *fmt; //自动变量值
int val; //自动变量值
//结构体初始化函数
__main_block_impl_0(void *fp, strct __main_block_desc_0, const char*_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = & NSConcreatStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}
注意
block只会捕获使用到的变量,表达式中没有使用到的变量不会被自动追加
__block说明符
看到上面👆捕获变量的源码,可以看出来,block中捕获的自动变量仅捕获自动变量的值。因此在block内部重写该自动变量也不会改变原来捕获的自动变量值,因此,当我们给捕获的自动变量赋值时 就会产生编译错误
静态变量、静态全局变量、全局变量
我们可以在block中直接访问或修改静态全局变量/全局变量 这是没有任何改变的,因此可以在block转换后的函数中直接使用,而无需在结构体中保留、变量
但是对于静态变量,因为block转换后的函数已经在静态变量所处的函数之外,出了访问的作用域
//结构体中
__main_block_impl_0 {
...
int * static_val; //block内部保留 静态变量的指针地址
}
//执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
(*static_val) *= 3 //
}
因此 block将静态变量的指针传递给__main_block_impl_0
结构体的构造函数,并保留在结构体中,这是超出作用范围最简单的访问方法了
注意
对于自动变量为什么没有采用类似静态变量的访问方式呢??
Block可以截获超过作用域被截获的对象的自动变量,变量作用域结束的,原来的自动变量被废弃,因此block中超过变量作用域的对象就如同静态变量一样,无法通过指针进行访问了
__block修饰符
在OC中存在以下几种存储域类说明符:
- typedef
- extern
- static
- auto
- register
__block
就类似static、auto、register说明符,用于指定将变量设置到哪个存储区域中。(auto表示作为自动变量存储到栈中、static作为静态变量存储到数据区)
//源代码
__block int val = 10
void(^blk)(void) = ^{val = 1}
block结构体持有了指向__block变量结构体指针
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
__Block_byref_val_0 *val;
//结构体初始化函数
__main_block_impl_0(void *fp, strct __main_block_desc_0, __Block_byref_val_0 *_val, int flags=0) : val(_val->_forwarding) {
impl.isa = & NSConcreatStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}
__block变量
__block变量如同block一般变为__Block_byref_val_0
结构体实例
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; //相当于原自动变量
}
//其初始化
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
}
结构体持有了相当持有原自动变量的成员变量, __Block_byref_val_0
结构体实例的成员变量__forwarding
持有指向该实例自身的指针。通过 __forwarding
访问成员变量val。(成员变量val相当于原自动变量)
函数
^{val=1}
//源代码转换为
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = _cself->val;
(val->__forwarding->val) = 1
}
注意:
- 为什么将__block变量写为单独的结构体而不写在block结构体中? 因是为了可以在多个block中使用相同的__block变量
- 为了什么使用了__block变量 就可以改写值? 因为当变量添加了__block前缀,此时声明的就直接是一个结构体 我们之后访问、修改不论是在block内外 都是用的这个结构体
本章忽略内容:
- Block超出变量作用域存在原因
- block变量结构体成员forwading存在理由