前言:
以下文字都是摘自ios与osx多线程和内存管理一书,方便以后自己过来查阅的.
本文梗概:Block的实质:Block究竟是什么?本节将通过Block的实现进一步帮大家加深理解。。
1. Blocks的实现
:
1.3 __block说明符
:
我们再来回顾前面截获自动变量的例子。^{printf(fmt, val);}
该源码转换结果如下:
static void __main_block_func_0 (struct __main_block_impl_0 *__cself)
{
const char *fmt = __cself->fmt;
int val = __cself->val;
printf(fmt, val);
}
发现,Block中所使用的被截获自动变量就如“带有自动变量值的匿名函数”所说,仅截获自动变量的值。Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出给被截获自动变量赋值的操作时,便产生编译错误。
不过这样就无法在Block中保存值了,极为不便。
解决这个问题有2种方法。第一种:c语言中有一个变量,允许Block改写值。具体如下:
- 静态变量
- 静态全局变量
全局变量
虽然Block语法的匿名函数部分简单地变换了c语言函数,但从这个变换的函数中访问静态全局变量/全局变量并没有任何改变,可直接使用。
但是静态变量的情况下,转换后的函数原本就设置在含有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;
strict __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;
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = {
0.
sizeof(struct __main_block_impl_0)
};
int main()
{
static int static_val = 3;
blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &static_val);
return 0;
}
这个结果大家很熟悉,对静态全局变量static_global_val
和全局变量global_val
的访问与转换前完全相同。
以下摘出Block使用该变量的部分。
static void __main_block_func_0(struct __main_block_impl_0 *__cself){
int *static_val = __cself->static_val;
(*static_val) *= 3;
}
使用静态变量static_val
的指针对其进行访问。将静态变量static_val
的指针传递给__main_block_impl_0
结构体的构造函数并保存。这是超出作用域使用变量的最简单方法。
静态变量的这种方法似乎也适用于自动变量的访问。但我们为什么没有这么做?
实际上,在由Block语法生成的值Block上,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃。因此Block中超过变量作用域而存在的变量痛静态变量一样,将不能通过指针访问原来的自动变量。这些在下节详细说明。
解决Block中不能保存值这一问题的第二种方法是使用__block说明符
。更准确的表述方式为__block存储域类说明符
。c语言中有以下存储域类说明符:
- typedef
- extern
- static
- auto
- register
__block
说明符类似于static
,auto
和register
说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto
表示作为自动变量存储在栈中,static
表示作为静态变量存储在数据区中。
下面我们来实际使用__block
说明符,用它来指定Block中想要变更值的自动变量。
__block int val = 10;
void (^blk)(void) = ^{val = 1;};
转换后:
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;
__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 *vla = __cself->val;
(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(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long 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()
{
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
return 0;
}
只是在自动变量上附加了__block
说明符,代码量就急剧增加。__block int val = 10;
这个__block
变量val
是怎样转换过来的?
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
我们发现,它竟然变为了结构体实例。__block
变量也同Block一样变成__Block_byref_val_0
结构体类型的自动变量,即栈上生成的__Block_byref_val_0
结构体实例。该变量初始化为10,,且这个值夜出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。
该结构体声明如下:
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 *vla = __cself->val;
(val->__forwarding->val) = 1;
}
刚刚在Block中向静态变量赋值时,使用了指向该静态变量的指针。而向__block变量
赋值要比这个更复杂。Block的__main_block_impl_0
结构体实例持有指向__block变量
的__Block_byref_val_0
结构体实例的指针。__Block_byref_val_0
结构体实例的成员变量__forwarding
持有指向改实例自身的指针。通过成员变量__forwarding
访问成员变量val
.(成员变量val是该实例自身持有的变量,它相当于原自动变量。)如图3-1所示:
究竟为什么会有成员变量__forwarding
呢?这个问题,我们留到下节来详细说明。
另外,__block变量
的__Block_byref_val_0
结构体并不在Block用__main_block_impl_0
结构体中,这样做是为了多个Block中使用__block变量
。我们看一下下面的代码:
__block int val = 10;
void (^blk0)(void) = ^{val = 0;};
void (^blk1)(void) = ^{val = 1};
Block类型变量blk0盒blk1访问__block变量val。我们吧这2部分代码转换的结果摘录出来。
__Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
blk0 = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val,0x22000000);
blk1 = &__main_block_impl_1(
__main_block_func_1, &__main_block_desc_1_DATA, &val, 0x22000000);
两个Block都使用了__Block_byref_val_0
结构体实例val的指针。这样一来就可以从多个Block中使用同一个__block变量
。当然,反过来从一个Block中使用多个__block变量
也是可以的。只要增加Block的结构体成员变量与构造函数的参数,便可对应使用多个__block变量
。
到此大概能能够理解__block
变量了。下节主要说明之前跳过的部分的内容:
- Block超出变量作用域可存在的理由
__block
变量的结构体成员变量__forwarding
存在的理由