一说起Block,肯定就要说它的循环引用.这是个老生常谈的问题,也是我们面试中经常被问到的.循环引用就说:我中有你,你中有我.如图所示:

今天我们将会通过底层代码,更加深刻的了解循环引用.
typedef void (^HHBlock) (void);
@interface Person : NSObject
@property (nonatomic,copy)HHBlock block;
@property (nonatomic,assign)int age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc]init];
person.age = 20;
person.block = ^{
NSLog(@"age is %d",person.age);
};
NSLog(@"-----------------");
}
return 0;
}
我们在Person类中添加HHBlock , age
两个属性,然后再 main 函数中访问 age 属性会怎么样呢?很显然这样会发生循环引用,我们在开发中也经常遇到这样的问题.他们之间的引用关系如下图:

查看底层代码发现block内部的确对 person 进行了强引用:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong person; // 对 person 产生了强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
那我们怎么解决循环引用呢?我们可以试着分析一下,如下图所示,Block 内部和 Person 产生了互相强引用,那我们只要把其中的一条线变成弱引用就可以了.

如果把第二条线变成弱引用,那么就会存在 person 对象还没销毁,block 就提前释放的情况,可能会影响 block 的正常使用,所以我们还是要 对
_block 成员变量
保持强引用.所以最佳的解决办法就是把第一条线变成弱引用.
- 在ARC环境下,有两种方法可以解决这种循环引用的问题:
1: __weak
2:__unsafe_unretained
我们使用 __weak 然后看一下底层代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc]init];
__weak Person *weakPerson = person;
person.age = 20;
person.block = ^{
NSLog(@"my age is %d",weakPerson.age);
};
NSLog(@"-----------------");
person.block();
}
return 0;
}
=====================================================
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson; // block 内部对person的引用的确变成了弱引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
把__weak
转换成__unsafe_unretained
再看看:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__unsafe_unretained weakPerson; // 底层已经转换为 __unsafe_unretained
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__unsafe_unretained _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
发现底层已经变成__unsafe_unretained
,好尴尬,看不出来效果,我们运行一下看看效果:

既然
__weak 和 __unsafe_unretained
都是弱引用,那么他们之间有什么区别呢?从名字我们可以看出来
__unsafe_retained
是不安全的,所谓的不安全就是:如果 Person 对象释放了,使用在 block 内部会把使用__weak
修饰的 person 指针设置为 nil
,而不会把使用__unsafe_retained
修饰的指针设置为nil,所以会出现野指针的情况.
- 使用
__block
也可以解决循环引用的情况.
使用__block解决循环引用
使用__block
修饰person
后会出现3个对象Person对象,Block对象(__main_block_impl_0),__block对象(__Block_byref_weakPerson_0)
,他们之间的关系如下:
__block对象的循环引用关系
如上图所示,他们三者之间互相强引用,已经形成了一个⭕️,如果要使用__block
就要及时切断切断一条线:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc]init];
__block Person *weakPerson = person;
person.age = 20;
person.block = ^{
NSLog(@"my age is %d",weakPerson.age);
//在 block 内部,把 person 对象置为nil
weakPerson = nil;
};
NSLog(@"-----------------");
person.block();
}
return 0;
}
运行以上代码发现 person 对象成功释放了,说明他们三者之间的循环引用被打断了.我们思考一下,在 block 内部把 person 置为nil,切断的是哪根线呢?
总结:
有三种方法可以解决 Block 的循环引用问题
1: __weak
2: __unsafe_unretained
3: __block
一般我们都使用__weak
,因为__unsafe_unretained
是不安全的,使用__block
的前提条件是 Block 必须要调用,并且要在 Block内部把对象置为 nil.
以上讲的都是 ARC环境下,如果在 MRC环境下怎么解决 Block 循环引用问题呢?
因为在 MRC 环境下没有
__weak
关键字,所以在 MRC 下有两种解决方式:
1: __unsafe_unretained
2: __block
需要注意的是,在 MRC 下, __blcok 对象内部是不会强引用外部对象的,所以在 MRC 下不需要把对象置为nil,也不是必须调用 blcok.
网友评论