(四)Block之 __block修饰符及其存储域
2017-10-21 本文已影响39人
madaoCN
相关文章
- (一)Block的实质初探
- (二)Block之存储域 NSConcreteStackBlock,NSConcreteGlobalBlock,NSConcreteMallocBlock
- (三)Block之截获变量和对象
- (四)Block之 __block修饰符及其存储域
引言
int i = 0; // 不加__block修饰符将编译错误
void (^blk)() = ^() {
i = 1; // error!!!!!
};
当我们在Block语法内修改自动变量时,没有加__block修饰符,将会得到一个编译错误
为了保存Block对值的操作,我们可以定义:
- 全局变量
- 静态全局变量
- 静态局部变量
int global_var = 1; // 全局变量
static int static_global_var = 2; // 静态全局变量
{
static int static_var = 3; // 静态局部变量
void (^blk)() = ^() {
global_var++;
static_global_var++;
static_var++;
printf("%d\n", global_var); // 输出2
printf("%d\n", static_global_var); // 输出3
printf("%d\n", static_var); // 输出4
};
blk();
}
输出结果如我们所愿,值修改成功了
我们进行编码转换,看下发生了什么事情
int global_var = 1;
static int static_global_var = 2;
/ * Block结构体 */
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_var;
// ......省略构造函数
};
/ * Block方法 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_var = __cself->static_var; // 注意这里!!!!!
global_var++;
static_global_var++;
(*static_var)++; // 注意这里!!!!!
printf("%d\n", global_var);
printf("%d\n", static_global_var);
printf("%d\n", (*static_var));
}
可见,对全局变量
和静态全局变量
的访问依然不作变化,静态局部变量
则使用指针对其访问(其指针传递给__main_block_impl_0
Block结构体,通过保存的指针在Block语法内来访问自动变量)
我们也可以给变量加上__block
修饰符,达到同样的目的
__block修饰符
__block int i = 10; // 不加__block修饰符将编译错误
void (^blk)() = ^() {
i = 20;
};
添加__block
修饰符后,如愿达到了目的,接下来看看发生了什么事情
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; // 注意这里!!!!!
int __flags;
int __size;
int i;
};
/ * Block结构体 */
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // 注意这里!!!!!
构造函数
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
__Block_byref_i_0 *_i,
int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/ * Block方法 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // 注意这里!!!!!
(i->__forwarding->i) = 20;// 注意这里!!!!!
}
/ * __block int i = 0; 转换后代码*/
__Block_byref_i_0 i = {0, //__isa
(__Block_byref_i_0 *)&i, // __forwarding 注意这里!!!!
0, // flag
sizeof(__Block_byref_i_0), // size
10 // 变量i
};
- 添加
__block
修饰符后, 代码量增加了不少,自动变量i
被转换成了结构体__Block_byref_i_0
,值被保存在了这个结构体内部 - 和上面的对
静态自动变量
赋值(*static_var)++;
,使用指向改变量的指针不同,__block
的赋值操作变成了(i->__forwarding->i) = 20;
- 从代码转换结果看
__Block_byref_i_0 i
变量的初始化,__forwarding
被赋值(__Block_byref_i_0 *)&i,
,也就是__forwarding
指针间接访问,这个指针指向自身,如下图所示
__main_block_impl_0
那么,为什么会有__forwarding
呢? 下面将作出解释
__block变量存储域
已知,使用了__block变量的Block从栈复制到堆时,__block变量也会受到影响,总结如下
__block变量存储域 | Block从栈复制到堆 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
- 当栈上的Block被复制到堆上时,__block变量也会随之复制,并且Block持有该变量
- 多个Block中使用__block变量时,任何一个Block被复制到堆时,__block变量也会一并复制到堆并被持有,其余Block被复制时,仅需要__block 变量的引用计数
- 和使用引用计数一样,在堆上的Block被废弃,它所引用的__block变量将被释放
理解Block作用域之后,我们发现这和OC引用计数方式管理方式一样,使用__block修饰符来持有对象,当Block被废弃之后,__block修饰变量也随之释放
当Block被复制到堆之后,会将自身的__forwarding
指针更新,依然指向“最新”的自己,这样就保证了在栈上或者堆上都能正确访问对应变量
而__forwarding
指针的作用正在这里,上文的
__Block_byref_i_0 *i = __cself->i;
(i->__forwarding->i) = 20;
- 当Block处于栈时,__forwarding指向自身,
i->__forwarding->i
访问的是栈上的结构体的变量i
- 当Block被复制到堆时,
i->__forwarding->i
访问的是堆上的结构体的变量i
由此
__forwarding 保证在栈上或者堆上都能正确访问对应变量