[譯] Swift 閉包和代理中的保留週期

O_3發表於2017-04-05

Swift 閉包和代理中的保留週期

讓我們一起來弄明白 [weak self]、[unowned self] 和 weak var

[譯] Swift 閉包和代理中的保留週期

迷航的船

疑問

當我第一次遇到閉包和代理時,我注意到人們在閉包中宣告 [weak self],在委託屬性前宣告 weak var。我想知道為什麼。

前提

這不是一篇給初學者的教程。以下列表是我期望我的讀者知道的。

  1. 如何通過代理在兩個檢視控制器間傳值
  2. 在 Swift 中使用 ARC 管理記憶體
  3. 閉包捕獲列表
  4. 協議作為型別

如果你不是很熟悉上面的知識點,別擔心。我以前的文章和 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()複製程式碼

我來梳理一下

首先,receivingVCReceivingVC() 的一個例項,ReceivingVC() 有一個屬性 ReceivingVC()

然後,sendingVCSendingVC() 的一個例項,SendingVC() 有一個屬性 delegate

我畫了個簡單的關係圖,方便你們理解。

[譯] Swift 閉包和代理中的保留週期

迴圈強引用和記憶體洩漏

請確保你熟悉強引用和弱引用的涵義。如果不瞭解的話,你可以看這篇文章 Make Memory Management Great Again

在上面的例子中,ReceivingVCSendingVC 之間存在強引用。雖然 ReceivingVC 引用的是 delegate 屬性,而不是 SendingVC它仍被認為引用了該物件,因為你必須持有一個物件才能訪問它的方法和屬性。

如果您嘗試下面的程式碼,不會有任何反應。

var receivingVC = nil // 不會被釋放複製程式碼

介紹 weak var

我們唯一要做的就是把 weak 寫在 var delegate 的前面。

class SendingVC {
    weak var delegate: SendDataDelegate?
}複製程式碼

沒有 weak let 這種寫法。當你使用 weak 來宣告時,就像上面代理屬性一樣,這個屬性應該是可選的和可變的,以便將其置為 nil,或者賦值給這個代理屬性。因此,let 是不允許的。

讓我們來看看現在的引用關係圖。

[譯] Swift 閉包和代理中的保留週期

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()複製程式碼

我們來看下關係圖。

[譯] Swift 閉包和代理中的保留週期

沒有迴圈引用,是單向的。

正如你所注意到的,閉包的程式碼塊是整個類的單獨的實體

讓我們銷燬它

bobClass = nil // 被銷燬了。。。☠️複製程式碼

一切執行正常。但是,現實和理想總是有差距的。如果這個閉包持有該屬性的引用怎麼辦?

init() {
    self.bobClosure = { print("\(**self.name**) the Developer") }
}複製程式碼

我們看下關係圖

[譯] Swift 閉包和代理中的保留週期

閉包和 BobClass 間的迴圈強引用

讓我們銷燬它

bobClass = nil // 沒有被銷燬 ?複製程式碼

這很嚴重。我們需要做些事情。

捕獲列表

我們有一種方法可以將閉包與物件(self)間的引用關係置為 “weak”,那就是捕獲列表。

self.bobClosure = { [weak self] in
    print("\(self?.name) the Developer")
}複製程式碼

閉包拿走並複製了這物件(self)。但是,這個閉包只是弱持有了它。

我們看下關係圖。

[譯] Swift 閉包和代理中的保留週期

閉包弱持有了物件,因此,也弱持有了該屬性。

如果你不理解 [] 在上面的閉包中做了什麼,你可以看完這篇 Swift capture list 文章再回來。

一些奇怪的事情

突然間,self(物件)成了可選型別,寫成 self?.name。這就是為什麼閉包能夠通過在程式碼塊中將 self 置為 nil 來斷開引用(綠色箭頭),因為關係是 weak。因此,Swift 會自動將 self 轉換為可選型別。

我們來試著銷燬它

bobClass = nil // I'm gone...☠️複製程式碼

[譯] Swift 閉包和代理中的保留週期

很好

祝賀,你完成了第二個。但是,還有一個:Unowned。

Unowned

你們中一些人可能會說,“還有一個?不是吧,Bob”。是的,還有一個。你已經走了很長的路,讓我們堅持走完。

weakunowned 是一樣的,除了一點,不像我們所看到的 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 開發者的資源

原始碼

關於 Bob the Developer

我正在努力提供價格合理的教育工作,並且我已經開始 iOS 開發的教學。bobthedeveloper.ioFacebook, Instagram, YouTube, LinkedIn


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章