一,概述
Block是“带有自动变量值得匿名函数”,Block看上去好像很特别,但它实际上是作为极普通的C语言源代码来处理的。通过clang -rewrite-objc 资源文件 命令,得到 资源文件.cpp文件,分析可以知道block内部的实现原理。
一,编译举例
比如如下代码:
int main()
{
static int static_val = 3;
int (^blk)() = ^{
(*static_val) = 30;
return 0;
}
int j = blk();
}
经过编译转换后为:
int main()
{
static int static_val = 3;
int (*blk)() = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
int j = ((int (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
从中我们可以看出,block 就是一个结构体__main_block_impl_0,这个结构体中包含了一个isa指针,方法的地址等,如下:
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;
}
};
其中__block_impl为:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
方法实现__main_block_func_0为:
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val = 10;
static_global_val = 20;
(*static_val) = 30;
return 0;
}
以及__main_block_desc_0:
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)};
二,截获自动变量值,及改写被截获自动变量值的方法
1. 第一种,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;
}
转换C语言后:
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 int __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val = 10;
static_global_val = 20;
(*static_val) = 30;
return 0;
}
总结,静态全局变量 和全局变量,是直接拿过来用,静态变量是在block结构体中拿到其地址(指针)来改变其值。
2. 第二种方法是使用__block说明符,更准确的表述方式为“__block存储域类说明符”
C语言有以下存储域说明符
1. typedef
2. extern
3. static 存到数据区中
4. auto 存到栈
5. register
三,__blcok变量编译
使用__block的代码如下:
int main()
{
typedef void (^blk_t)();
int __block var = 1;
blk_t blk = ^{
var = 2;
};
return 0;
}
经过编译后:
int main()
{
typedef void (*blk_t)();
blk_t blk;
__Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 1};
blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));
return 0;
}
可以看出__block变量其实被转换为__Block_byref_var_0结构体:
其中__forwarding的作用:__block变量用结构体成员变量__forwarding可以实现__block变量配置在栈上还是堆上时都能够正确访问__block变量。
struct __Block_byref_var_0 {
void *__isa;
__Block_byref_var_0 *__forwarding;
int __flags;
int __size;
int var;
};
方法的实现为:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_var_0 *var = __cself->var; // bound by ref
(var->__forwarding->var) = 2;
}
另外__block变量需要控制生命周期,又出现了下面两个由栈copy到堆的copy方法,和销毁的dispose方法
static void __main_block_copy_0(
struct __main_block_impl_0*dst,
struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(
struct __main_block_impl_0*src
) {
_Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}
四,什么时候栈上的Block会复制到堆
(将block从栈上复制到堆上相当消耗CPU)
1. 调用Block的copy实例方法
2. Block作为函数返回值返回时
3. 将Blcok赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
4. 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时
五,__block变量存储域
使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响。
总结如下:
1. __block变量的存储域 在栈时,从栈复制到堆并被Blcok持有。
2. __block变量的存储域 在堆时,被Blcok持有。
3. 当多个Block中使用__block变量时,当任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。
__block修饰符可以和__strong,__weak,__unsafe_unretained修饰符共用,但是和__autoreleasing修饰符共用会产生编译错误。
六,Block循环引用
1. 引发循环引用的例子:
- object对象的强引用MyBlock类型成员blk_。在init方法中有MyBlock类型的myblock,使用附有__strong修饰符的id类型变量self,并且myblock赋值给object的成员变量blk_,此时myblock(或者blk_)由栈复制到堆,并持有所使用的self。self持有myblock,myblock持有self,发生了循环引用。
如下代码中block内没有使用self也同样截获了self,引起了循环引用。
typedef void (^blk_t)(); @interface MyObject : NSObjec{ blk_t blk_ id obj_ } @end @implementation MyObject -(id)init{ self = [super init]; blk_ = ^{ NSLog(@“obj_ = %@”,obj_); // 其中obj_相当于self->obj_,也会截获sel } return self; } @end
2. 怎样避免循环引用
- 使用__weak或__unsafe_unretain修饰符
使用__block修饰符,例:
- (id)init { self = [super init]; __block id temp = self; blk_ = ^{ temp = nil; } } - (void)execBlock { blk_(); }
如果不调用execBlock实例方法,便会导致循环引用。
3. 使用__block变量和__weak及__unsafe_unretained修饰符避免循环引用的方法比较
使用__block变量优点如下:
- 通过__block变量可控制对象的持有期间
- 在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可(不必担心悬垂指针)
使用Block时可动态地决定是否将nil或其他对象赋值在__block变量中
使用__block变量的缺点如下:
- 为避免循环引用必须执行Block.