一. Blocks的认识
什么是Blocks?
Blocks是C语言的拓展功能(带有局部变量的匿名函数)。局部变量跟block的关系就像成员变量和类。
所谓匿名函数,就是不带名称的函数。C语言的标准是不存在这样的函数的。
//声明一个函数
int func( int count);
//直接调用函数时
int result = func(10);
//用函数指针调用函数时
int result =(* func)(10);
在C语言中,不使用想赋值的函数的名称,就无法取得该函数的地址。而通过Blocks,就能够使用不带名称的函数。
Block的语法
完整的Block语法与C语言函数相比有两点不同:没有函数名和带有“^”。
^ 返回值类型 参数列表 表达式
^ int (int count){return count + 1;}
但是Block语法可以省略返回值类型和参数列表。省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果没有return语句就使用void类型。
//省略后的Block
^{printf("Blocks\n");}
Blocks的使用
我们先来看看C语言函数的使用:
int func(int count){
return count + 1;
}
//将函数地址赋值给函数指针类型变量
int (*funcptr)(int) = &func;
声明Block类型变量以及变量之间的赋值:
int (^blk)(int) = ^(int count){return count + 1};
int (^blk1)(int);
blk1 = blk;
Block类型变量作为函数参数和函数返回值:
int (^func())(int){
return ^(int count){return count + 1};
}
使用typedef声明Block类型变量
typedef int (^blk_t)(int);
int func(blk_t blk){
return blk(10);
}
也可以像C语言变量一样使用,使用Block的指针类型变量。
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1};
blk_t *blkptr = &blk;
(*blkptr)(10);
Block截获的自动变量值
Block表达式截获所使用的自动变量的值是该自动变量的瞬间值,不能在Block中改变(编译器会报错),只能输出该瞬间值。如果在Block中修改该值,要加上__block修饰符。
int let = 10;
__block int var = 20;
void (^blk)(void) = ^{
printf("%d,%d",let,var); //输出结果 10,21
var = 22; //这边也可以改变var的值,而let不可以
};
let = 11;
var = 21;
blk();
二. Block的底层实现
通过clang(LLVM编译器)将Block语法的源代码转换为C++源代码来了解Block的底层实现。
- 在Xcode创建Block.m文件
- 打开终端,cd到存放Block.m的文件夹
- 输入clang -rewrite-objc Block.m,生成Block.cpp
int mian(){
void (^blk)(void) = ^{
printf("Block");
};
blk();
return 0;
}
打开.cpp拉到最后面,这些才是我们转换后的源码:
struct __block_impl {
void *isa; //对象都有的isa指针,指向元类
int Flags; //标志
int Reserved; //今后版本升级所需的区域
void *FuncPtr; //函数指针
};
/* block结构体 */
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
// block构造函数
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/* block中的代码 */
//__cself相当于Objective-C中的self(用OC类的说法就是self 执行__mian_block_func_0)
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
printf("Block");
}
static struct __mian_block_desc_0 {
size_t reserved; //今后版本升级所需的区域
size_t Block_size; //Block的大小
} __mian_block_desc_0_DATA = { //这部分为赋值
0,
sizeof(struct __mian_block_impl_0)
};
/* mian函数 */
int mian(){
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我们先看看main函数里面的代码,我们可简化成以下代码:
//创建Block
struct __mian_block_impl_0 temp = __mian_block_impl_0(__mian_block_func_0, &__mian_block_desc_0_DATA));
struct __mian_block_impl_0 *blk = &tmp;
//执行Block
(*blk->impl.FuncPtr)(blk);
该源代码将__mian_block_impl_0结构体类型的自动变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。通俗地讲,就是将__mian_block_impl_0结构体实例的指针赋给变量blk。
__mian_block_impl_0构造函数(即源代码中的Block构造函数)的第一个参数是Block语法转换的C语言指针,第二个参数是作为静态全局变量初始化的__mian_block_desc_0结构体实例指针。
我们再来看执行Block的部分,这是简单地使用函数指针调用函数。在构造函数中,由Block语法转换的__mian_block_func_0函数的指针被赋值给成员变量FuncPtr,并以Block自身为参数。
补充重要的一点,前面提到的:
impl.isa = &_NSConcreteStackBlock;
其实,block就是Objective-C对象。这边isa指针指向的_NSConcreteStackBlock,该Block的信息放置于_NSConcreteStackBlock中(相当于子类和父类的关系)。
截获自动变量值
我们先来看看截获自动变量值的源代码,理解自动变量值为什么只是一个瞬间值。
Objective-C代码:
int mian(){
int val = 10;
void (^blk)(void) = ^{
printf("val = %d",val); //输出结果 val= 10
};
val = 11;
blk();
return 0;
}
clang后的源代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
/ * Block结构体 */
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
int val; //比之前的结构多出该成员变量val
/* 构造函数 */
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/ * Block中执行的函数 */
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("val = %d",val); //打印的是Block自己的成员变量的值
}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
int mian(){
int val = 10;
//构造函数中传入val的值
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, val));
val = 11;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通过源码,我们看出,Block语法表达式中使用的自动变量被作为成员变量追加到__mian_block_impl_0结构体中,并且会从构造函数中传入val的值作为参数。改写该值报编译错误的原因我们也由想而知。
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
int val; //比之前的结构多出该成员变量val
/* 构造函数 */
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在后面的输出函数中,也是输出Block的成员变量val的值。由此可见,在__mian_block_impl_0结构体实例中,自动变量值被截获。
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("val = %d",val); //打印的是Block自己的成员变量的值
}
__blcok说明符的值
那如何才能改变Block中截获的自动变量?有两种方法:
① C语言的静态变量、静态全局变量、全局变量允许Block改写值
int global_val = 1; //全局变量
static int static_global_val = 2; //静态全局变量
int mian(){
static int static_val = 10; //静态变量
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *=3;
};
blk();
return 0;
}
转换后的源码为:
int global_val = 1;
static int static_global_val = 2;
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
int *static_val; //保存的是静态变量的指针
__mian_block_impl_0(void *fp, struct __mian_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 __mian_block_func_0(struct __mian_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 __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
int mian(){
static int static_val = 10;
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
对静态全局变量static_global_val和全局变量global_val的访问和转换前完全相同。
而静态变量static_val,是使用其指针进行访问的。将静态变量static_val的指针传递给__mian_block_impl_0结构体的构造函数并保存。那为什么自动变量不用指针访问的方法呢?因为自动变量存放在栈帧中,变量作用域结束的同时,自动变量被废弃,Block将不能用指针访问到原来的自动变量。
② 使用__block说明符
__block说明符类似于static、auto和register说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。
int mian(){
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
转换后的源代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
/* __block变量生成的结构体 */
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; //保存的val的值
};
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__mian_block_impl_0(void *fp, struct __mian_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 __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __mian_block_copy_0(struct __mian_block_impl_0*dst, struct __mian_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __mian_block_dispose_0(struct __mian_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __mian_block_impl_0*, struct __mian_block_impl_0*);
void (*dispose)(struct __mian_block_impl_0*);
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __mian_block_impl_0),
__mian_block_copy_0,
__mian_block_dispose_0
};
int mian(){
//创建__Block_byref_val_0结构体实例
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
//将__Block_byref_val_0结构体指针作为参数
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
加上__block说明符,我们发现__block变量同Block一样变成__Block_byref_val_0结构体类型的自动变量。__Block_byref_val_0结构体的地址作为参数传入Block的构造函数中。因此,Block的__mian_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
至于__Block_byref_val_0结构体为什么在Block外创建,是为了方便多个Block使用。
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding; //指向自身的指针
int __flags;
int __size;
int val; //保存的val的值
};
在给__bloc变量赋值的源码中:
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
__Block_byref_val_0结构体实例的成员变量__forwarding指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。(这边有点绕,要弄懂先要弄清楚Block的存储域)
Block的存储域
Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。那Block超出变量作用域是如何继续存在的呢?
要了解这点,我们要先知道Block的三种类型以及它们的存储域:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
通过声明全局变量blk来使用Block语法,那该Block类为_NSConcreteGlobalBlock。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。另外,在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
综上,以下情况Block为_NSConcreteGlobalBlock类对象,除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。
- 记述全局变量的地方有Block语法时
-
Block语法的表达式中不使用应截获的自动变量时
配置在全局变量的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block,如果所属的变量作用域结束,该Block和__block变量都会被废弃。针对这个问题,Blocks提供了将Block和__block变量从栈上复制到堆上的方法。
typedef int (^blk_t)(int)
blk_t func(int rate){
return ^(int count){return rate * count};
}
blk_t func(int rate){
blk_t tmp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA,rate);
/*
* 将配置在栈上的Block的结构体实例赋值给tmp
*/
tmp = objc_retainBlock(tmp);
/*
* 等价于tmp = _Block_copy(tmp)
* 将栈上的Block赋值到堆上,并将堆上的地址作为指针赋值给tmp
*/
return objc_autoreleaseReturnValue(tmp);
/*
* 将堆上的Block对象注册到autoreleasepool并返回
*/
}
将Block作为函数返回值时,编译器会自动生成复制到堆上的代码。当向方法或函数的参数中传递Block时,需要我们手动生成代码,调用Block的copy方法。
不过以下方法或函数不用手动调用:
- Cocoa框架的方法且方法名中含有usingBlock等时(比如NSArray类的enumerateObjectsUsingBlock实例方法)
- GCD的API
当使用__block变量的Block从栈上复制到堆上时,这些__block变量也全部被从栈复制到堆,Block持有__block变量。
使用__block变量的Block持有__block变量(__block变量作为Block的一个成员变量)。如果Block被废弃,它所持有的__block变量也就被释放。栈上的__block用结构体实例在从栈复制到堆时,会将成员变量__forwarding的值替换为复制目标堆上的__block用结构体实例的地址。这样不管__block变量配置在栈上还是堆上,都可以正确访问该变量。
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
两个++val均可转换成以下源码:
++(val.__forwarding->val);
第一个val为复制到堆上的__block变量用结构体实例,会通过指针访问到自己。第二个val为复制前栈上__block变量用结构体实例,通过指针访问到堆上的__block变量用结构体实例。通过该功能,都可以顺利访问同一个__block变量。
截获对象
前面我们截获的是普通值或带__block说明符的值,那要是在Block语法中使用对象呢?
typedef void(^blk_t)(id obj);
blk_t blk;
void setupBlk(){
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld",[array count]);
} copy];
}
int main(){
setupBlk();
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
return 0;
}
输出结果为:
array count = 1
array count = 2
array count = 3
本来array的变量域结束的同时,array被废弃,其强引用失效,因此赋值给变量array的NSMutableArray类的对象必定被释放并废弃。但是代码运行正常,由此可见赋值给array的NSMutableArray类的对象在最后Block的执行部分超出其变量作用域而存在。通过编译器转换后的源代码如下:
typedef void(*blk_t)(id obj);
blk_t blk;
struct __setupBlk_block_impl_0 {
struct __block_impl impl;
struct __setupBlk_block_desc_0* Desc;
id array;
/* 构造方法 */
__setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __setupBlk_block_func_0(struct __setupBlk_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9v_3rmg01ds0zj0gpwn04shlxwc0000gn_T_Block_097da3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src) {
//相当于retain方法
_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
//相当于release方法
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __setupBlk_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __setupBlk_block_impl_0*, struct __setupBlk_block_impl_0*);
void (*dispose)(struct __setupBlk_block_impl_0*);
} __setupBlk_block_desc_0_DATA = {
0,
sizeof(struct __setupBlk_block_impl_0),
__setupBlk_block_copy_0,
__setupBlk_block_dispose_0
};
void setupBlk(){
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__setupBlk_block_impl_0((void *)__setupBlk_block_func_0, &__setupBlk_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}
int main(){
setupBlk();
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
}
我们可以发现,Block结构体中截获__strong修饰符的成员变量array:
struct __setupBlk_block_impl_0 {
struct __block_impl impl;
struct __setupBlk_block_desc_0* Desc;
id array; //等同于id __strong array
}
在ARC需要遵守的规则中,对象型变量不能作为C语言结构体的成员变量。因为编译器不知道何时进行C语言结构体的初始化和废弃,不能很好地管理内存。但是Objective-C的运行时库能够准确把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block用结构体中即使含有附有__strong或__weak修饰符的变量,也能恰当地进行初始化和废弃。
在该Block源码中,__setupBlk_block_desc_0中增加了copy和dispose函数指针的成员变量,以及作为指针赋值给该成员变量的__setupBlk_block_copy_0和__setupBlk_block_dipose_0方法。
__setupBlk_block_copy_0函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中,__setupBlk_block_dipose_0函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。简单说,就是对Block结构体中成员变量array赋值和废弃。当然,在Block从栈复制到堆时以及堆上的Block被废弃时才会调用这些函数。
如果在在array前面加上__block说明符呢?
转换的源码如下:
struct __Block_byref_array_0 {
void *__isa;
__Block_byref_array_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id array;
};
struct __setupBlk_block_impl_0 {
struct __block_impl impl;
struct __setupBlk_block_desc_0* Desc;
__Block_byref_array_0 *array;
__setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src{
_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}
同Block截获对象一样,当__block变量从栈复制到堆时,使用_Block_object_assign函数,持有赋值给__block变量的对象。当堆上的__block变量被废弃时,使用_Block_object_dispose函数,释放赋值给__blcok变量的对象。
由此可见,只要堆上的Block的成员变量(__block变量或者对象)存在,那么该对象就会继续处于被持有状态。
如果是__weak和__block的组合呢?
当超出作用域时,__weak的对象也会置于nil。所以会得出结果:
array count = 0
array count = 0
array count = 0
如果不调用copy方法呢?
执行该代码后,程序会强制结束。因为只有调用_Block_copy才能持有截获的附有__strong修饰符的对象类型的自动变量值,否则即使截获了对象,没有retain,它也会随着变量作用域的结束而被废弃。
所以,Block中使用对象类型自动变量时,除以下情形外,需要调用Block的copy方法。
- Block作为函数返回值返回时
- 将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量
- 向方法名中含有usingBlock的Cocoa框架方法或GCD的API传递Block时
三. Block的循环引用
如果在Block中使用__strong 修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。这样容易引起循环引用。
typedef void(^blk_t)(void);
@interface MyObject()
{
blk_t _blk;
}
@end
@implementation MyObject
- (instancetype)init{
self = [super init];
_blk = ^{NSLog(@"self = %@",self);};
return self;
}
- (void)dealloc{
NSLog(@"dealloc");
}
//在main函数创建MyObject
int main(){
id obj = [[MyObject alloc] init];
return 0;
}
MyObject类对象的成员变量_blk持有Block的强引用,即MyObject对象持有Block。由于Block是赋值给成员变量_blk,Block会由栈复制到堆,并持有所使用的self。所以造成循环引用。
那避免循环引用有哪些方法呢?
① 声明附有__weak修饰符的变量,并将self赋值使用。
- (instancetype)init{
self = [super init];
id __weak tmp = self;
_blk = ^{NSLog(@"self = %@",tmp);};
return self;
}
②使用__block变量(可用于ARC无效时,不过缺点是必须执行Block,将临时变量tmp置为nil)
- (instancetype)init{
self = [super init];
id __block tmp = self;
_blk = ^{
NSLog(@"self = %@",tmp);
tmp = nil;
};
return self;
}
还有一点需要注意的时,__block说明符解决循环引用时的方式在ARC和MRC是不一样的。MRC中,Block从栈复制到堆时,Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain,若没有附有__block,会被retain。所以以下可以解决循环引用。
- (instancetype)init{
self = [super init];
id __block tmp = self;
_blk = ^{
NSLog(@"self = %@",tmp);
};
return self;
}