泊學高清視頻
泊閱文檔
咱們來了解一下這類reference cycle是如何發生的,以及對應的解決方法。swift
首先,咱們定義一個類,用來表示HTML DOM元素:spa
class HTMLElment { let name: String let text: String? init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { print("\(self.name) is being deinitialized") } }
其中name表示HTML標籤的名字,text表示標籤之間的文本內容,因爲不是全部HTML標籤之間都有文本,所以,它是一個String?。接下來,咱們能夠像這樣定一個HTMLElement對象。code
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title")
若是,咱們但願能夠把HTMLElement表明的對象渲染出來,例如:<h1>Title</h1>。爲了能夠在將來定製這個渲染操做,咱們決定給HTMLElement添加一個Closure member,它不接受任何參數,返回咱們但願渲染的字符串:orm
class HTMLElment { let name: String let text: String? var asHTML: Void -> String = { // WRONG SYNTAX!!! if let text = self.text { return "<\(self.name)>\(self.text)</\(self.name)>" } else { return "<\(self.name)>" } } // Omit for simplicity... }
當咱們這樣編寫asHTML的時候,Swift會告訴咱們發生了一些語法錯誤:視頻
這是因爲,Swift沒法確認當咱們在Closure中使用self時,它已經被完整的初始化過了。若是咱們須要這種初始化約束,咱們能夠把asHTML定義爲lazy。對象
class HTMLElment { // Omit for simplicity lazy var asHTML: Void -> String = { // Omit for simplicity... } // Omit for simplicity... }
**「lazy能夠確保一個成員只在類對象被完整初始化過以後,才能使用。」
——特別提示**ci
定義了asHTML以後,咱們就能夠觀察h1的構建和釋放過程了。首先,咱們看使用asHTML以前:文檔
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title") h1 = nil
在Playground結果裏,咱們能夠看到h1先被建立,然後被銷燬的過程(由於HTMLElement的deinit方法被調用了)。字符串
而當咱們在讓h1等於nil前,使用asHTML的話,狀況就不一樣了:get
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title") h1.asHTML h1 = nil
這時咱們就發現,HTMLElement的deinit再也不被調用了。
根據咱們以前的經驗,必定是在某處發生了reference cycle。爲了可以搞清楚這個問題,咱們先來看一下在h1等於nil以前,相關對象之間的關係:
h1是咱們定義的strong reference。Closure做爲一個引用類型,它有本身的對象,所以asHTML也是一個strong reference。因爲asHTML「捕獲」了HTMLElement的self,所以HTMLElement的引用計數是2。當h1爲nil時,asHTML對closure的引用和closure對self的「捕獲」就造成了一個reference cycle。
**「儘管在closure內部,使用了屢次selfclosure對self的捕獲僅發生1次(引用計數只加1)。」
——特別提示**
本質上來講,closure做爲一個引用類型,解決reference cycle的方式和解決類對象之間的reference cycle是同樣的,若是引發reference cycle的"捕獲"不能爲nil,就把它定義爲unowned,不然,定義爲weak。而指定「捕獲」方式的地方,叫作closure的capture list。咱們把asHTML修改爲下面這樣:
class HTMLElment { let name: String let text: String? lazy var asHTML: Void -> String = { // text // Capture list [unowned self] in if let text = self.text { return "<\(self.name)>\(self.text)</\(self.name)>" } else { return "<\(self.name)>" } } // Omit for simplicity... }
咱們使用一對 [] 表示closure的capture list,因爲「捕獲」到的self不能爲nil(不然closure也不存在了),所以咱們把它定義爲unowned self。在咱們這樣作以後,當h1爲nil時,對象之間的關係就變成了這樣:
因爲HTMLElement沒有了strong reference,所以它會被ARC釋放掉,進而asHTML引用的closure也會變成「孤魂野鬼」,ARC固然也不會放過它。所以,closure和類對象間的循環引用問題就解決了。
在這裏,關於closure capture list,咱們要多說兩點:
若是closure帶有完整的類型描述,capture list必須寫在參數列表前面;
若是咱們要在capture list裏添加多個成員,用逗號把它們分隔開;
class HTMLElment {
let name: String let text: String? lazy var asHTML: Void -> String = { // text // Capture list [unowned self /*, other capture member*/] () -> String in if let text = self.text { return "<\(self.name)>\(self.text)</\(self.name)>" } else { return "<\(self.name)>" } } // Omit for simplicity...
}
**「當一個類中存在訪問數據成員的closure member時,務必要謹慎處理它有可能帶來的reference cycle問題。」——特別提示**