- 原文地址:Swift Retention Cycle in Closures and Delegate
- 原文作者:Bob Lee
- 譯文出自:掘金翻譯計劃
- 譯者:oOatuo
- 校對者:Deepmissea, gy134340
Swift 閉包和代理中的保留週期
讓我們一起來弄明白 [weak self]、[unowned self] 和 weak var
迷航的船
疑問
當我第一次遇到閉包和代理時,我注意到人們在閉包中宣告 [weak self]
,在委託屬性前宣告 weak var
。我想知道為什麼。
前提
這不是一篇給初學者的教程。以下列表是我期望我的讀者知道的。
- 如何通過代理在兩個檢視控制器間傳值
- 在 Swift 中使用 ARC 管理記憶體
- 閉包捕獲列表
- 協議作為型別
如果你不是很熟悉上面的知識點,別擔心。我以前的文章和 YouTube 教程涵蓋了所有這些知識。你可以在 這裡 找到所需的知識以及我高效的開發工具。
Objectives
首先,你將瞭解到為什麼我們要在代理中使用 weak var
。接著,你將知道何時在閉包中使用 [weak self]
和 [unowned self]
。
我想要這篇內容更加進階,我們一起進步吧。
代理中的保留週期
首先,我們建立一個 SendDataDelegate
的代理。
protocol SendDataDelegate: class {}複製程式碼
然後,我們建立一個 SendingVC
的類,並新增一個型別是 SendDataDelegate?
的屬性。
class SendingVC {
var delegate: SendDataDelegate?
}複製程式碼
最後,將這個代理指向另一個類。
class ReceivingVC: SendDataDelegate {
lazy var sendingVC: SendingVC = {
let vc = SendingVC()
vc.delegate = self // self refers to ReceivingVC object
return vc
}()
deinit {
print("I'm well gone, bruh")
}
}複製程式碼
你可能會被 lazy
的初始化方法所困擾。那麼,你可以先自己研究下,或者可以等我的下一篇文章。
現在,我們建立一個例項。
var receivingVC: ReceivingVC? = ReceivingVC()複製程式碼
我來梳理一下
首先,receivingVC
是 ReceivingVC()
的一個例項,ReceivingVC()
有一個屬性 ReceivingVC()
。
然後,sendingVC
是 SendingVC()
的一個例項,SendingVC()
有一個屬性 delegate
。
我畫了個簡單的關係圖,方便你們理解。
迴圈強引用和記憶體洩漏
請確保你熟悉強引用和弱引用的涵義。如果不瞭解的話,你可以看這篇文章 Make Memory Management Great Again。
在上面的例子中,ReceivingVC
和 SendingVC
之間存在強引用。雖然 ReceivingVC
引用的是 delegate
屬性,而不是 SendingVC
,它仍被認為引用了該物件,因為你必須持有一個物件才能訪問它的方法和屬性。
如果您嘗試下面的程式碼,不會有任何反應。
var receivingVC = nil // 不會被釋放複製程式碼
介紹 weak var
我們唯一要做的就是把 weak
寫在 var delegate
的前面。
class SendingVC {
weak var delegate: SendDataDelegate?
}複製程式碼
沒有 weak let
這種寫法。當你使用 weak
來宣告時,就像上面代理屬性一樣,這個屬性應該是可選的和可變的,以便將其置為 nil
,或者賦值給這個代理屬性。因此,let
是不允許的。
讓我們來看看現在的引用關係圖。
delegate 持有 ReceivingVC 的弱引用。
讓我們試著釋放它。
receivingVC = nil
// "I'm well gone, bruh"複製程式碼
你只需在代理的物件是個類的時候使用 weak
。Swift 中的結構體和列舉型別是值型別,不是引用型別,所以它們不會造成迴圈強引用。如果你不熟悉協議,可以看下這篇文章:介紹面向協議程式設計。
恭喜!你已經完成了第一個目標,讓我們來看下一個。
閉包中的保留週期
現在,讓我們一起看下第二個目標。我們的目的是弄明白為什麼要在一個閉包中使用 [weak self]
。首先,我們建立一個 BobClass
的類。它包含兩個 String
和 (() -> ())?
型別的屬性。
class BobClass {
var bobClosure: (() -> ())?
var name = “Bob”
init() {
self.bobClosure = { print(“Bob the Developer”) }
}
deinit {
print(“I’m gone... ☠️”)
}
}複製程式碼
建立一個例項。
var bobClass: BobClass? = BobClass()複製程式碼
我們來看下關係圖。
沒有迴圈引用,是單向的。
正如你所注意到的,閉包的程式碼塊是整個類的單獨的實體
讓我們銷燬它
bobClass = nil // 被銷燬了。。。☠️複製程式碼
一切執行正常。但是,現實和理想總是有差距的。如果這個閉包持有該屬性的引用怎麼辦?
init() {
self.bobClosure = { print("\(**self.name**) the Developer") }
}複製程式碼
我們看下關係圖
閉包和 BobClass 間的迴圈強引用
讓我們銷燬它
bobClass = nil // 沒有被銷燬 ?複製程式碼
這很嚴重。我們需要做些事情。
捕獲列表
我們有一種方法可以將閉包與物件(self)間的引用關係置為 “weak”,那就是捕獲列表。
self.bobClosure = { [weak self] in
print("\(self?.name) the Developer")
}複製程式碼
閉包拿走並複製了這物件(self)。但是,這個閉包只是弱持有了它。
我們看下關係圖。
閉包弱持有了物件,因此,也弱持有了該屬性。
如果你不理解 []
在上面的閉包中做了什麼,你可以看完這篇 Swift capture list 文章再回來。
一些奇怪的事情
突然間,self
(物件)成了可選型別,寫成 self?.name
。這就是為什麼閉包能夠通過在程式碼塊中將 self
置為 nil
來斷開引用(綠色箭頭),因為關係是 weak
。因此,Swift 會自動將 self
轉換為可選型別。
我們來試著銷燬它
bobClass = nil // I'm gone...☠️複製程式碼
很好
祝賀,你完成了第二個。但是,還有一個:Unowned。
Unowned
你們中一些人可能會說,“還有一個?不是吧,Bob”。是的,還有一個。你已經走了很長的路,讓我們堅持走完。
weak
和 unowned
是一樣的,除了一點,不像我們所看到的 weak
那樣,在閉包中,unowned
不會自動將 self
轉化成 可選型別。
例如,如果我建立一個正常的例項而不是一個可選的型別。
var neverNilClass: BobClass = BobClass()複製程式碼
這裡沒有理由去使用 weak
,因為如果你這樣做,這個閉包會捕獲 self
作為一個可選的型別,然後你需要像下面那樣去解包,這其實沒必要。
self.bobClosure = { [weak self] in
guard let object = self else {
return
}
print("\(object.name) the Developer")
}複製程式碼
相反,如果你 100% 確定 self
永遠不會變成 'nil',那麼只需這樣:
self.bobClosure = { [unowned self] in
print("\(self.name) the Developer")
}複製程式碼
就這樣。
寫在最後
我希望你們看得開心!另外,我最近將我的部落格的名字從 iOS Geek Community 改成了 Bob the Developer。有兩個原因,第一,之前的名字不符合只有我一個作者的事實。第二,我想將我個人品牌提高到一定的程度,讓你們能夠將 Swift 和 Bob the Developer 聯絡起來。
如果你有所收穫,請點選下面或左邊的 ❤️ ,我會很感激。我之前在想要不要放那些關係圖,因為它需要花費更多的時間,但為了我可愛的 Medium 讀者們,一切都值得。
資源
關於 Bob the Developer
我正在努力提供價格合理的教育工作,並且我已經開始 iOS 開發的教學。bobthedeveloper.ioFacebook, Instagram, YouTube, LinkedIn
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃。