- 原文地址:Swift Retention Cycle in Closures and Delegate
- 原文做者:Bob Lee
- 譯文出自:掘金翻譯計劃
- 譯者:oOatuo
- 校對者:Deepmissea, gy134340
迷航的船javascript
當我第一次遇到閉包和代理時,我注意到人們在閉包中聲明 [weak self]
,在委託屬性前聲明 weak var
。我想知道爲何。html
這不是一篇給初學者的教程。如下列表是我指望個人讀者知道的。前端
若是你不是很熟悉上面的知識點,別擔憂。我之前的文章和 YouTube 教程涵蓋了全部這些知識。你能夠在 這裏 找到所需的知識以及我高效的開發工具。java
首先,你將瞭解到爲何咱們要在代理中使用 weak var
。接着,你將知道什麼時候在閉包中使用 [weak self]
和 [unowned self]
。react
我想要這篇內容更加進階,咱們一塊兒進步吧。android
首先,咱們建立一個 SendDataDelegate
的代理。ios
protocol SendDataDelegate: class {}複製代碼
而後,咱們建立一個 SendingVC
的類,並添加一個類型是 SendDataDelegate?
的屬性。git
class SendingVC {
var delegate: SendDataDelegate?
}複製代碼
最後,將這個代理指向另外一個類。github
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 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。
大家中一些人可能會說,「還有一個?不是吧,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 讀者們,一切都值得。
我正在努力提供價格合理的教育工做,而且我已經開始 iOS 開發的教學。bobthedeveloper.ioFacebook, Instagram, YouTube, LinkedIn
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。