什么是循环引用
Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为 nil 等),就可以确保内存使用不出现问题。
但是,所有的自动引用计数机制都有一个从理论上无法绕过的限制,那就是循环引用 (retain cycle) 的情况。
假设我们有两个类 A 和 B, 它们之中分别有一个存储属性持有对方:
class A {
let b: B
init() {
b = B()
b.a = self
}
deinit {
println("A deinit")
}
}
class B {
var a: A? = nil
deinit {
println("B deinit")
}
}
在 A 的初始化方法中,我们生成了一个 B 的实例并将其存储在属性中。然后我们又将 A 的实例赋值给了 b.a。这样 a.b 和 b.a 将在初始化的时候形成一个引用循环。现在当有第三方的调用初始化了 A,然后即使立即将其释放,A 和 B 两个类实例的 deinit 方法也不会被调用,说明它们并没有被释放。
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!)
-> Bool {
// Override point for customization after application launch.
var obj: A? = A()
obj = nil
// 内存没有释放
return true
}
在 Swift 里防止循环引用
为了防止这种人神共愤的悲剧的发生,我们必须给编译器一点提示,表明我们不希望它们互相持有。一般来说我们习惯希望 "被动" 的一方不要去持有 "主动" 的一方。在这里 b.a 里对 A 的实例的持有是由 A 的方法设定的,我们在之后直接使用的也是 A 的实例,因此认为 b 是被动的一方。可以将上面的 class B 的声明改为:
class B {
weak var a: A? = nil
deinit {
println("B deinit")
}
}
在 var a 前面加上了 weak,向编译器说明我们不希望持有 a。这时,当 obj 指向 nil 时,整个环境中就没有对 A 的这个实例的持有了,于是这个实例可以得到释放。接着,这个被释放的实例上对 b 的引用 a.b 也随着这次释放结束了作用域,所以 b 的引用也将归零,得到释放。添加 weak 后的输出:
A deinit
B deinit
总体说下来,我理解为在声明的时候,被动一方需要加上weak
其他一个例子
比如实现如下一个自定义控件
定义整个控件为RangeSlider
类
import UIKit
class RangeSlider: UIControl {
}
把这个控件的按钮定义为一个继承自CALayer
的类,起名字叫RangeSliderThumbLayer
,因为有两个,所以有
let lowerThumbLayer = RangeSliderThumbLayer()
let upperThumbLayer = RangeSliderThumbLayer()
更上边合成后RangeSlider
类现在为这个样子
import UIKit
class RangeSlider: UIControl {
let lowerThumbLayer = RangeSliderThumbLayer()
let upperThumbLayer = RangeSliderThumbLayer()
}
那么RangeSliderThumbLayer
应该是什么样子呢,如下:
import UIKit
import QuartzCore
class RangeSliderThumbLayer: CALayer {
weak var rangeSlider: RangeSlider?
}
在这里,rangeSlider
引用回父 range slider。由于 RangeSlider 有两个 thumb layer,所以将这里的引用设置位 weak,避免循环引用。
那么此时在RangeSlider
类中加入以下就不会出现问题了
lowerThumbLayer.rangeSlider = self
upperThumbLayer.rangeSlider = self
此时,RangeSliderThumbLayer
是被动方,所以在其中写上weak