扒一扒swift中的unowned和weak下
上一篇写到了swift中引起Reference cycle(循环引用)的原因,和一些不同场景下处理的办法,那这篇我就来叨叨下平时项目里会经常遇到的Closuer(闭包)里引用self对象的处理办法
先看个例子
class HTMLElment {
let name: String
let text: String?
var asHTML: Void -> String = {
// WRONG SYNTAX!!!
if let text = self.text {
return "<\(self.name)>\(self.text)"
} else {
return "<\(self.name)>"
}
} // Omit for simplicity...
}
注: lazy可以确保一个成员只能在类对象在完整初始化过以后,才能使用
定义了asHTML之后,我们就可以观察h1的构建和释放过程了。首先,我们看使用asHTML之前:
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title")h1 = nil
在Playground结果里,我们可以看到h1先被创建,而后被销毁的过程(因为HTMLElement的deinit方法被调用了)。
而当我们在让h1等于nil前,使用asHTML的话,情况就不同了:
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title")
h1.asHTML
h1 = nil
这时我们就发现,HTMLElement的deinit不再被调用了。
根据之前说过的,我们推断,一定是某处发生了循环引用了。
h1是我们定义的strong reference。Closure作为一个引用类型,它有自己的对象,因此asHTML也是一个strong reference。由于asHTML“捕获”了HTMLElement的self,因此HTMLElement的引用计数是2。当h1为nil时,asHTML对closure的引用和closure对self的“捕获”就形成了一个reference cycle。
注: 尽管在closure内部,使用了多次self,closure对self的捕获仅发生1次(引用计数只加1)。
Closure中发生reference cycle的解决办法:
本质上来说,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)"
} else {
return "<\(self.name)>"
}
}
}
我们使用一对 [] 表示closure的capture list,由于“捕获”到的self不能为nil(否则closure也不存在了),因此我们把它定义为unowned self。在我们这样做之后,当h1为nil时,对象之间的关系就变成了这样:
由于HTMLElement没有了strong reference,因此它会被ARC释放掉,进而asHTML引用的closure也会变成“孤魂野鬼”,ARC当然也不会放过它。因此,closure和类对象间的循环引用问题就解决了。
- 如果closure带有完整的类型描述,capture list必须写在参数列表前面:
- ** 如果我们要在capture list里添加多个成员,用逗号把它们分隔开,例如:**
class HTMLElment {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
// Capture list
[unowned self /*, other capture member*/] () -> String in
if let text = self.text {
return "<\(self.name)>\(self.text)"
} else {
return "<\(self.name)>"
}
}
}
在利用Capture list解决了Closure中循环引用的问题以后,还要注意一点: 如果在某个Clusure中捕获到到self可以为nil,那我们就要回到上一篇开头所讲的那样,判断一下self,然后再进行处理了
{[weak self] in
if let weSelf = self{ 执行代码}
}