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)'?