上一节我们讲到了Block的本质以及其实现原理,这一节我们就来看一下Block中的一些特性的实现原理是怎样的。
截获自动变量值
之前第一节我们讲到过Block可以截获自动变量
的值,当我们定义一个自动变量,在执行Block语法之后,即使改变
Block中使用的自动变量值
也不会影响
Block执行时自动变量的值。下面我们就用源码举例说明。
int main(){
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt,val);};
val = 2;
fmt = "these values were changed. val = %d\n";
blk();
return 0;
}
我们通过clang命令将其转换如下:
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, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);}
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(){
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
fmt = "these values were changed. val = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
与之前的转换稍有不同,我们来看看其中的差异,首先我们看到Block语法中使用的自动变量
被作为成员变量
追加到了__main_block_impl_0
结构体中。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
};
请注意Block中没有使用
的自动变量不会被追加
,比如源代码中的变量dmy
,Block的自动变量截获
只针对Block中使用
的自动变量
。下面我们再来看看初始化
该结构体实例
的构造函数
的差异。
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val)
在初始化结构体实例
时,根据传递给构造体函数
的参数
对由自动变量
追加的成员变量
进行初始化。以下通过构造函数调用确认其参数。
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
使用执行Block语法时的自动变量fmt和val
来初始化__main_block_impl_0结构体实例
,即在该源码中__main_block_impl_0结构体实例
的初始化如下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
fmt = "val = %d\n";
val = 10;
由此可知,在__main_block_impl_0结构体实例
中,自动变量被截获。下面我们再来看一下使用Block的匿名函数的实现:
^{printf(fmt,val);};
该源代码可转换为以下函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);}
在转换后的源代码中,截获到__main_block_impl_0结构体实例
的成员变量
上的自动变量
,这些变量在Block语法表达式之前
被声明定义。
总的来说在执行Block语法时,Block语法表达式所使用的自动变量值
被保存
到Block的结构体实例
中,所以之后即使更改其值也不会造成影响。
__block说明符
通过上面的讲解我们明白了Block截获自动变量值的原理,那么如果我们在Block语法中更改自动变量的值
会怎样呢?
int main(){
int val = 10;
void (^blk)(void) = ^{val = 1;};
blk();
return 0;
}
该源代码会产生以下编译错误:
Variable is not assignable (missing __block type specifier)
如前所述,因为在实现上不能改写被截获自动变量的值,所以编译器在编译过程中检出这种操作便会产生编译错误,这样一来就无法在Block中保存值了,极为不便。
在Block的第一篇我们提到过在函数的多次调用之间
能够传递值
的变量有三种,静态变量(静态局部变量)
,静态全局变量
,全局变量
。
我们现在就来看一下这三种变量在Block中是如何运作的:
int global_val = 1;
static int static_global_val = 2;
int main(){
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
return 0;
}
这段源码使用Block改写静态变量static_val
,静态全局变量static_global_val
和全局变量global_val
。该源代码转换后如下:
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
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(){
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
return 0;
}
我们可以看到对静态全局变量
和全局变量
的访问与转换前相同,因为这两个变量的作用域可以作用整个文件,Block可以直接访问,但是对于静态局部变量static_val
就不太一样了。我们来看一下转换后的函数是如何使用该变量的:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
(*static_val) *= 3;
}
使用了静态变量
的指针
对其进行访问。将静态变量static_val
的指针
传给__main_block_impl_0结构体
的构造函数并保存。这是超出作用域
使用变量最简单的方法。
这时大家一定会有疑问,为什么不用对待静态局部变量
的方式对待局部变量
呢?
这是因为由Block语法生成的Block上
可以存有超过其变量作用域
的被截获的自动变量
。如果变量作用域结束
了,原来的自动变量会被废弃
,因此我们无法使用指针
去访问之前的自动变量
了,但是静态变量
则不同它储存在程序专用的数据存储区
,它的作用域
虽然只在函数中
,超出作用域
之后虽然我们无法直接访问其值,但是它的地址
依旧存在,所以我们可以使用其指针
去访问。
下面还有一种方式去解决Block中不能保存值的问题,那就是使用__block说明符
,全名叫做“__block存储域类说明符”
。
__block说明符
类似于static,auto和register说明符
,他们用于指定将变量设置到指定的存储域
中。例如auto
表示作为自动变量存储在栈
中,static
表示作为静态变量存储在数据区
中。
我们来实际使用一下__block
说明符,指定想在Block中变更的自动变量
。
int main(){
__block int val = 3;
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
源代码转换后如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(){
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
只是增加了一个__block说明符
,代码量便极具增加,那么这个__block说明符
是如何转换的呢?
__block int val = 3;
转换后的代码如下:
__Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
3
};
被__block
修饰的变量竟然变成了结构体实例
,即栈上生成的__Block_byref_val_0结构体实例
。这个变量初始化为3,这个值也出现在结构体实例
的初始化中,这意味着该结构体持有相当于原自动变量
的成员变量
。该结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
这个结构体中最后的成员变量val
就是相当于原来的自动变量
,从名字也可以看出来这一点。那么最关键的赋值代码又是怎样呢?
^{
val = 1;
};
该代码转化如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
在上文的静态变量
赋值的时候使用了静态变量
的指针
直接访问地址去修改变量的值,而__block变量
赋值要比这个复杂一些,Block的__main_block_impl_0结构体实例
持有指向__block变量
的__Block_byref_val_0结构体实例
的指针
也就是__main_block_impl_0成员变量
中的__Block_byref_val_0 *val
。
而这个__Block_byref_val_0结构体实例
的成员变量__forwarding
持有指向该实例自身
的指针
。通过成员变量__forwarding
访问成员变量val
(相当于原自动变量)。
(val->__forwarding->val) = 1;
就如同这句代码一样,简单明了的表达了访问的方式。至于成员变量__forwarding
存在的意义又是什么呢?我们留在下一节再进行说明。
网友评论