Fork me on GitHub

iOS中Block循环引用刨根问底

序言

Blocks是苹果出的轻量型回调方式,使用起来既简洁,又方便。不过就是会产生一个问题:循环引用。进而会导致内存释放不了,造成内存泄漏。那到底怎么样才会产生循环引用呢?如何解决呢?

这篇文章我们就用多个案例从本质上去解析到底啥是循环引用

案例解析

1
typedef void(^Blk_t)(void);

案例1

我们首先先来看一个循环引用的案例
声明一个全局变量

1
@property (nonatomic,copy) Blk_t blk;

1
2
3
4
5
6
7
- (void)cycleRetainMethod1
{
self.blk = ^void(void){
[self doSomething];
};
self.blk();
}

在UIViewController中调用这个方法,则会导致循环引用。

接下来让我们分析为什么会循环引用,看下图:

分析循环引用其实只要通过以上图都可以分析出来是否产生了循环引用。很典型就是相互持有产生了一个闭环,最终导致谁也释放不了,从而内存泄漏。
一般我们是通过以下方法解决:

1
2
3
4
5
6
7
8
- (void)cycleRetainMethod1
{
__weak typeof(self) this = self;
self.blk = ^void(void){
[this doSomething];
};
self.blk();
}

通过持有即可解决循环引用。

案例2

1
2
3
4
5
6
7
8
- (void)cycleRetainMethod2
{

Blk_t block = ^ void(void){
[self doSomething];
};
block();
}

当我们在UIViewController调用这个方法的时候是否会产生循环引用呢?

答案是不会的

我们也是通过以上的图示来画一个

因为block变量是局部变量,所以UIViewController是不会持有的,从而导致原来的闭环断开,所以不会造成内存泄漏。

案例3

这个案例是一个对比案例,如果通过简单的画图可能不足余说明问题,所以我们结合编译后的源码就可以彻底说明

Person.m文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@implementation Person

- (instancetype)initWithBlock:(Block)block
{
if (self = [super init]) {
_blk = block;
}
return self;
}

- (void)Block:(Block)block
{
_blk = block;
NSLog(@"====%@",block);
}


- (void)execute
{
_blk(@"回调数据");
}

-(void)dealloc{
NSLog(@"person释放了");
}
@end

  • 第一种写法
1
2
3
4
5
6
7
8
9
10
- (void)cycleRetainMethod3
{
//第一种写法
_person = [[Person alloc] initWithBlock:^(NSString *str) {
[self doSomething];

}];
[person execute];

}
  • 第二种写法
1
2
3
4
5
6
7
8
9
- (void)cycleRetainMethod3
{
//第二种写法
_person2 = [[Person alloc] init];
[person2 Block:^(NSString *str) {
[self doSomething];
}];
[person2 execute];
}

以上是代码,大家觉着会产生循环引用吗?
答案是:第一种写法不会产生循环引用,第二种会产生循环引用

第二种通过案例1的图示很容易就能看出来这里产生了循环引用。
那第一种为啥没产生循环引用呢?

第一种写法,UIViewController持有_person,_person持有block所以形成一个闭环;而第二种写法由于Person.m的- (instancetype)initWithBlock:(Block)block的方法实现,在返回self之前对变量_blk赋值了。即_blk变量已经赋值,但是此时_person变量还没有产生,所以导致_person变量没有持有block

总结

Block循环引用其实分析起来很简单,不管中间经过多少变量,只要一层一层的分析,看看有没有持有,画一个简单的引用图即可,是闭环那就是循环引用。而且我们通过编译之后的源码也能非常清楚的知道了什么叫持有了吧。

0%