Tino又想吃肉了

一种常见的循环引用

Word count: 727Reading time: 2 min
2021/03/16

在使用swift的闭包时的一种常见的循环引用

什么是循环引用?

循环引用是指在程序中,两个互相持有对方强引用的对象,无法正常释放,从而导致内存泄漏。这在Block以及代理的使用中都很常见。

可以先看一下以下代码,你能发现哪里有问题吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class RetainCycle {
var str : String = "aaa"
var closure : ( () -> Void )!

init() {
closure = {
self.str = "Hello"
}
}

deinit {
print("deInit")
}
}
do{
let _ = RetainCycle()
}

如果你注意过内存泄漏的问题,那么你可以很快发现这里面出现了一个循环引用。

RetainCycle中,通过定义属性的方式持有了一个字符串变量,以及一个没有参数以及返回值的闭包,RetainCycle对他们都是强引用。而在init()方法中,我们在closure的实现里通过self取到了str变量,并对它进行赋值。此时闭包closure捕获了self,并对它持有强引用。

由于RetainCycleclosure同时都持有了对方的强引用,就导致了RetainCycle的实例无法正常释放,这时我们的deinit方法是不会执行的,因为内存一直被持有无法释放。

那么如何解决这个循环引用的问题呢?

很简单,我们只需要在closure捕获self之前,使用unownedweak关键字让它持有的是self的弱引用,这样就不会导致循环引用的问题了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RetainCycle {
var str : String = "aaa"
var closure : ( () -> Void )!

init() {
closure = { [weak self] in // [unowned self] in 同样可以
self?.str = "Hello"
}
}

deinit {
print("deInit")
}
}
do{
let _ = RetainCycle() // 打印出"deInit"
}

运行后我们可以在控制台看到deinit方法打印的”deInit”,说明RetainCycle实例已经被正常释放了。

weak和unowned的区别?

  • weak:在引用的对象被释放后,标记为weak的内容会自动地被置为nil,因此被标记为 weak 的变量一定是 Optional
  • unowned:unowned设置以后即使它引用的内容已经被释放了,它仍然会对被已经释放了的对象持有一个”无效的”引用,它不能是Optional值,也就是说不能被置为nil。这时候你通过这个引用去访问成员属性或者调用方法的话,就会造成程序崩溃。

结语

循环引用的问题是在开发过程中常见的问题,并且在面试中似乎也是面试官喜欢问的知识点。在block、delegate、closure的使用中我们都要注意防止循环引用的出现。同时,在两个类中同时已属性的方式持有对方,也会造成循环引用。
而内存泄漏,是导致程序性能差的一大元凶


Tino Wu

CATALOG
  1. 1. 什么是循环引用?
    1. 1.0.1. 可以先看一下以下代码,你能发现哪里有问题吗?
    2. 1.0.2. 那么如何解决这个循环引用的问题呢?
    3. 1.0.3. weak和unowned的区别?
    4. 1.0.4. 结语