在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
注意
Swift 有如下要求:只要在闭包内使用
self
的成员,就要用self.someProperty
或者self.someMethod()
(而不只是someProperty
或someMethod()
)。这提醒你可能会一不小心就捕获了self
。
定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是 weak
或 unowned
关键字,另一个元素是类实例的引用(例如 self
)或初始化过的变量(如 delegate = self.delegate
)。这些项在方括号中用逗号分开。
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 in
放在闭包最开始的地方:
lazy var someClosure = {
[unowned self, weak delegate = self.delegate] in
// 这里是闭包的函数体
}
弱引用和无主引用
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 无主引用
。
相反的,在被捕获的引用可能会变为 nil
时,将闭包内的捕获定义为 弱引用
。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 nil
。这使我们可以在闭包体内检查它们是否存在。
注意
如果被捕获的引用绝对不会变为
nil
,应该用无主引用,而不是弱引用。
前面的 HTMLElement
例子中,无主引用是正确的解决循环强引用的方法。这样编写 HTMLElement
类来避免循环强引用:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
上面的 HTMLElement
实现和之前的实现一致,除了在 asHTML
闭包中多了一个捕获列表。这里,捕获列表是 [unowned self]
,表示“将 self
捕获为无主引用而不是强引用”。
和之前一样,我们可以创建并打印 HTMLElement
实例:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印“<p>hello, world</p>”
使用捕获列表后引用关系如下图所示:
这一次,闭包以无主引用的形式捕获 self
,并不会持有 HTMLElement
实例的强引用。如果将 paragraph
赋值为 nil
,HTMLElement
实例将会被销毁,并能看到它的析构器打印出的消息:
paragraph = nil
// 打印“p is being deinitialized”
你可以查看 捕获列表 章节,获取更多关于捕获列表的信息。