泊学高清视频
泊阅文档
咱们来了解一下这类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问题。”——特别提示**