1、循环引用
-
如果两个类互相持有对方的强引用,就会出现循环引用的情况。
-
Swift 提供了两种解决循环引用的方法,弱引用和无主引用。
1.1 循环引用示例
-
比如存在一个学生类,一个班级类,学生类有一个属性叫学生所属的班级,而班级有一个属性是班长,它引用了一个学生类,它们都是可选型。
class Student { var name: String? var theClass: Class? deinit { print("Student deinit") } } class Class { var name: String? var classMonitor: Student? deinit { print("Class deinit") } }
-
接着我们构造一个学生类实例小明和一个班级类实例三年二班,为了能够销毁对象,把对象声明为可选型,这样在我们不需要对象的时候就可以给它赋值
nil
。var sneb: Class? = Class() sneb?.name = "sannianerban" var xiaoming: Student? = Student() xiaoming?.name = "xiaoming" sneb?.classMonitor = xiaoming xiaoming?.theClass = sneb
-
这时麻烦出现了,根据 ARC 规则,
xiaoming
和sneb
之间互相强引用对方,出现强引用。 -
在使用完
sneb
和xiaoming
这两个对象后,后面的代码再也不会用到sneb
和xiaoming
了,此时尝试把这两个变量置为nil
sneb = nil xiaoming = nil
-
但是你会发现控制台没有任何打印信息,没有任何一个析构器被调用,这是因为
sneb
和xiaoming
之间通过属性的方式强引用了对方,二者的引用计数都为 1,ARC 根据规则阻止了实例被销毁,从而造成了内存泄漏。
2、闭包循环引用
-
由于闭包也是引用类型的,闭包会持有内部的对象,当把闭包作为类的属性的时候,闭包与类之间也会出现循环引用的情况。
- 在一个闭包中使用其外层的类的属性时,编译器会强制你加上
self
关键字,以此作为 “持有” 的提醒。 - 将闭包类型的属性声明为
lazy
是因为通常声明一个闭包类型的属性是为了缓存某些操作,这些操作不一定会被执行,所以使用懒加载的方法提高运行效率,表示当闭包被调用的时候再进行初始化。
- 在一个闭包中使用其外层的类的属性时,编译器会强制你加上
-
要解决闭包引起的循环引用,Swift 中引入了 “捕获列表” 的概念,在闭包的参数列表中将闭包体中涉及的所有被 “持有” 对象声明为 “无主引用” 或者 “弱引用”,以逗号隔开。
-
将闭包作为类的属性是一种非常常见的做法,相比于定义一个方法,定义一个闭包类型的属性可以随时修改闭包中的执行内容,以此达到复用的效果,减少一个类中的代码量。同时闭包中的代码可以 “跨越” 不同的类型,替代传统的
delegate
用法。 -
相比于把闭包当作存储属性这样有点新奇的用法,闭包最常见的用途还是当作方法的参数,尤其是作为方法的最后一个参数。当闭包作为方法参数的时候,即便闭包中会持有
self
,也不会引起循环引用。
2.1 闭包循环引用示例
-
实例代码如下。
class Example { var num = 10 lazy var method: (Int) -> Int = { (i: Int) in return self.num + i } deinit { print("Example deinit") } }
-
method
是一个闭包类型的参数,在类的定义中被初始化为与类的属性num
进行加法运算。var example: Example? = Example() let _ = example?.method(3) example = nil
-
按照上面的方法使用
Example
,会发现析构器没有执行,与基本类型的属性一样,Example
类对method
闭包是强引用的,同时闭包的定义中又需要num
属性参与运算,此时闭包会通过self
属性 “持有” 类本身,这样产生了一个循环引用。 -
在
Example
中引入捕获列表后代码如下,此时Example
的循环引用就被打破了。class Example { var num = 10 lazy var method: (Int) -> Int = { [unowned self] (i: Int) in return self.num + i } deinit { print("Example deinit") } }
-
捕获列表除了可以设置
self
,还可以单独声明引用类型成员。class Example { var delegate: UITableViewDelegate? lazy var method: (Int) -> Int = { [weak delegate = self.delegate] (i: Int) in // 操作 delegate } }