自动引用计数(学习笔记)
注意
引用计数只应用于类的实例。结构体和枚举是值类型,不是引用类型,没有通过引用存储和传递。
ARC的工作机制
每次你创建一个类的实例,ARC 会分配一大块内存来存储这个实例的信息。这些内存中保留有实例的类型信息,以及该实例所有存储属性值的信息。
此外,当实例不需要时,ARC 会释放该实例所占用的内存,释放的内存用于其他用途。这确保类实例当它不在需要时,不会一直占用内存。
然而,如果 ARC 释放了正在使用的实例内存,那么它将不会访问实例的属性,或者调用实例的方法。确实,如果你试图访问该实例,你的app很可能会崩溃。
为了确保使用中的实例不会消失,ARC 会跟踪和计算当前实例被多少属性,常量和变量所引用。只要存在对该类实例的引用,ARC 将不会释放该实例。
为了使这些成为可能,无论你将实例分配给属性,常量或变量,它们都会创建该实例的强引用。之所以称之为“强”引用,是因为它会将实例保持住,只要强引用还在,实例是不允许被销毁的。
解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用( weak reference )和无主引用( unowned reference )。
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为 nil 的实例使用弱引用。相反,对于初始化赋值后再也不会被赋值为 nil 的实例,使用无主引用。在实例的生命周期中,当引用可能“没有值”的时候,就使用弱引用来避免循环引用。如同在无主引用中描述的那样,如果引用始终有值,则可以使用无主引用来代替
弱引用
弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 释放被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 weak 关键字表明这是一个弱引用。
由于弱引用不会强保持对实例的引用,所以说实例被释放了弱引用仍旧引用着这个实例也是有可能的。因此,ARC 会在被引用的实例被释放是自动地设置弱引用为 nil 。由于弱引用需要允许它们的值为 nil ,它们一定得是可选类型。
你可以检查弱引用的值是否存在,就像其他可选项的值一样,并且你将永远不会遇到“野指针”。
注意
在 ARC 给弱引用设置 nil 时不会调用属性观察者。
无主引用
和弱引用类似,无主引用不会牢牢保持住引用的实例。但是不像弱引用,总之,无主引用假定是永远有值的。因此,无主引用总是被定义为非可选类型。你可以在声明属性或者变量时,在前面加上关键字 unowned 表示这是一个无主引用。
由于无主引用是非可选类型,你不需要在使用它的时候将它展开。无主引用总是可以直接访问。不过 ARC 无法在实例被释放后将无主引用设为 nil ,因为非可选类型的变量不允许被赋值为 nil 。
注意
如果你试图在实例的被释放后访问无主引用,那么你将触发运行时错误。只有在你确保引用会一直引用实例的时候才使用无主引用。
还要注意的是,如果你试图访问引用的实例已经被释放了的无主引用,Swift 会确保程序直接崩溃。你不会因此而遭遇无法预期的行为。所以你应当避免这样的事情发生。
闭包的循环强引用
上面我们看到了循环强引用是如何在两个实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
循环强引用还会出现在你把一个闭包分配给类实例属性的时候,并且这个闭包中又捕获了这个实例。捕获可能发生于这个闭包函数体中访问了实例的某个属性,比如 self.someProperty ,或者这个闭包调用了一个实例的方法,例如 self.someMethod() 。这两种情况都导致了闭包 “捕获”了 self ,从而产生了循环强引用。
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把闭包赋值给了一个属性,你实际上是把一个引用赋值给了这个闭包。实质上,这跟之前上面的问题是一样的——两个强引用让彼此一直有效。总之,和两个类实例不同,这次一个是类实例和一个闭包互相引用。
解决闭包的循环强引用
你可以通过定义捕获列表作为闭包的定义来解决在闭包和类实例之间的循环强引用。捕获列表定义了当在闭包体里捕获一个或多个引用类型的规则。正如在两个类实例之间的循环强引用,声明每个捕获的引用为引用或无主引用而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
注意
Swift 要求你在闭包中引用self成员时使用 self.someProperty 或者 self.someMethod (而不只是 someProperty 或 someMethod )。这有助于提醒你可能会一不小心就捕获了 self 。
定义捕获列表
捕获列表中的每一项都由 weak 或 unowned 关键字与类实例的引用(如 self )或初始化过的变量(如 delegate = self.delegate! )成对组成。这些项写在方括号中用逗号分开。
把捕获列表放在形式参数和返回类型前边,如果它们存在的话:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
如果闭包没有指明形式参数列表或者返回类型,是因为它们会通过上下文推断,那么就把捕获列表放在关键字 in 前边,闭包最开始的地方:
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}