Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.
However, in a few cases ARC requires more information about the relationships between parts of your code in order to manage memory for you.
以上是 Swift 官方教程中,对 Swift 内存管理的解释。通常情况的部分很好理解,Swift 中 ARC 负责绝大部分的内存管理,ARC部分可以参考我的另一篇博客:iOS内存管理初探 – 引用计数、AutoRelease与ARC;但少数情况下,我们需要向ARC提供对象之间的关系来使其正确管理内存。那么少数情况是什么意思呢?这得从Swift中的循环强引用讲起。
Swift 中循环强引用的情景
- 类实例之间的循环强引用:类实例之间相互强引用了对方
如图所示的情况中,john指向的对象强引用了unit4A指向的对象,而unit4A指向的对象又强引用了john指向的对象。
- 闭包引起的循环强引用:
paragraph实例有一个成员asHTML强引用了一个闭包,而这个闭包中又捕获了self,意味着对self的强引用。
在ARC下,引用循环的情况是编译器无法自动解决的,这就是上文提到的少数情况。weak 和 unowned 的存才就是为了给编译器提供更多的信息,来打破循环引用。
利用 weak 和 unowned 杀死循环引用
weak
含义:weak 即弱引用,当把一个实例声明为弱引用时,此实例不会持有这个对象,即不会使对象的引用计数加1。当对象被废弃,其所有的弱引用会被置为 nil。
适用场景:
- 类实例之间的循环强引用:实例之间形成引用循环,且无法确定对象之间生命周期的依赖关系,即无法确定弱引用在某一时刻是否为空时,将可能为空的实例声明为弱引用。由于弱引用可能为 nil,应当声明为可选值。如:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? //公寓的房客可能为空所以声明为weak
deinit { print("Apartment \(unit) is being deinitialized") }
}
由于 tenant 是弱引用,当 tenant 引用的对象被销毁(如赋值 nil),tenant 被置为空,并且释放对 apartment的强引用,此时 apartment 所指对象就可以正常释放了。
- 闭包引起的循环强引用:在被捕获的引用可能会变为nil时,在捕获列表中,将闭包内的捕获定义为弱引用。如:
// someClosure 是类成员变量
lazy var someClosure: (Int, String) -> String = {
//self.delegate 可能在被捕获后变为 nil,所以定义为弱引用,unowned 解释见下文
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
由于 self.delegate 指向的是外部对象,生命周期与self无关,所以可能在被捕获后变为nil。(delegate 一般都声明为weak以避免循环引用)
unowned
含义:无主引用,与弱引用一样,当把一个实例声明为无主引用时,此实例不会持有这个对象,即不会使对象的引用计数加1。但与弱引用不同的是,当对象被废弃,其无主引用并不会被置为 nil。
适用场景:
- 类实例之间的循环强引用:实例之间形成引用循环,且可以确定某一实例之外的其他实例有相同或者更长的生命周期时,将此实例声明为无主引用。无主引用总被期望拥有值,当你访问对象被销毁的无主引用时,会触发运行时错误。
class Country {
let name: String
var capitalCity: City! //由于 capitalCity 的生命周期等于 country 的生命周期,可以隐式解析可选属性
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
虽然在这个例子中,capitalCity 与 country 的生命周期相同,理论上讲将其中任何一个声明为无主引用都可以打破引用循环,但 capitalCity 与 country 之间有从属关系,所以倾向于将“大”的一方,即 country 声明为无主引用。
- 闭包引起的循环强引用:在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
// someClosure 是类成员变量
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
self 与属于 self 的成员变量 someClosure 生命周期相同,同时销毁,所以声明为无主引用。
unowned(safe) 与 unowned(unsafe)
Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks—for example, for performance reasons. As with all unsafe operations, you take on the responsiblity for checking that code for safety.
You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.
在Swift中,写下 unowned 相当于 unowned(safe)。但在官方文档中提到, Swift 还提供了另一种不安全的无主引用 unowned(unsafe) 来禁用运行时的安全检查。运行时的安全检查就是使 unowned(safe) 安全的原因。
unowned(safe):当访问 unowned(safe) 类型的无主引用时,运行时会进行安全检查,如果对象已经废弃,将抛出异常并终止程序。
unowned(unsafe) :unowned(unsafe) 的作用效果实际上相当于 Objective-C 属性标示符中的 assign/unsafeunretained。访问 unowned(unsafe) 类型的无主引用时,运行时的安全检查被禁用,这时会有三种情况:
- 废弃对象内存还未被覆盖:程序正常运行
- 废弃对象内存被部分覆盖:奇怪的 crash,不确定的结果
- 废弃对象内存正好填入新的对象:由于向新的对象发送了调用旧对象方法的消息,会出现 unrecognized selector exceptions
总结
weak | unowned | |
---|---|---|
能杀死的强引用循环 | 实例之间/ 闭包引起 | 实例之间/ 闭包引起 |
实例是否能为nil | 都允许为nil | 其中一个允许为nil,另一个不允许为nil |
实例间的生命周期关系 | 无法确定 | 一个小于等于另一个 |
参考文章
The Swift Programming Language (Swift 3.1) : Automatic Reference Counting
What is the difference in Swift between 'unowned(safe)' and 'unowned(unsafe)'?