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 方法也不會被調用,說明它們並沒有被釋放。
因為即使 obj 不再持有 A 的這個對象,b 中的 b.a 依然引用着這個對象,導致它無法釋放。而進一步,a 中也持有着 b,導致 b 也無法釋放。在將 obj 設為 nil 之后,我們在代碼里再也拿不到對於這個對象的引用了,所以除非是殺掉整個進程,我們已經 永遠 也無法將它釋放了。多么悲傷的故事啊..
在 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和unowned
在 Swift 中除了 weak 以外,還有另一個沖着編譯器叫喊着類似的 "不要引用我" 的標識符,那就是 unowned 。它們的區別在哪里呢?如果您是一直寫 Objective-C 過來的,那么從表面的行為上來說 unowned 更像以前的 unsafe_unretained ,而 weak 就是以前的 weak 。
用通俗的話說,就是 unowned設置以后即使它原來引用的內容已經被釋放了,它仍然會保持對被已經釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值,也不會被指向 nil 。如果你嘗試調用這個引用的方法或者訪問成員屬性的話,程序就會崩潰。而 weak 則友好一些,在引用的內容被釋放后,標記為 weak 的成員將會自動地變成 nil (因此被標記為 @ weak 的變量一定需要是 Optional 值)。
關於兩者使用的選擇,Apple 給我們的建議是如果能夠確定在訪問時不會已被釋放的話,盡量使用 unowned ,如果存在被釋放的可能,那就選擇用 weak 。
日常工作中一般使用弱引用的最常見的場景有兩個:
- 設置
delegate時 - 在
self屬性存儲為閉包時,其中擁有對self引用時
其中,最常見的delegate使用時,可參考我這篇文章:
Swift代理造成內存泄漏的解決辦法
所以,weak使用上是更推薦一點的。
