上面的例子中的两个类的实例属性相互为对方保留一个强引用,所以我们就能看出来强引用循环是如何产生的,同样也能看出来上面我们如何使用弱引用和无主引用来断开强引用循环。
一个强引用循环同样也可以产生,如果我们将一个闭包分配给类的实例属性,闭包体捕获的是类的实例。这种闭包体捕获类实例可能发生是因为闭包体它读取了实例的属性,就像self.someProperty
,或者该闭包调用了实例中的方法,就像self.someMethod()
,不管在哪种情况下,这种捕获或读取会引起闭包”捕获“ self,从而会形成一个强引用循环。
强引用循环的产生是因为闭包,像类,这些类型都属于引用类型。当我们给属性分配一个闭包的时候,我们其实分配了一个引用给那个闭包。事实上,上面也会存在相同的问题,两个强引用也会保留对方存活,然而,不同的是两个类的实例,这时候类的实例和闭包相互保留对方存活着。
swift为这个问题提供了一个优雅的解决方案,叫做闭包捕获列表(closure capture list
),然而在我们学习了解如何用闭包捕获列表来断开强引用循环之前,很有必要先了解下这样的引用循环数如何产生的。
下面这个例子想我们展示了,如何赢一个引用self的闭包来创建一个强引用循环。下面这个例子定义了一个类HTMLElement
,用一种简单的模型表示 HTML 文档中的一个单独的元素。
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
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类定义了一个name属性来表示这个元素的名称,例如代表头部元素的 “h1” ,代表段落的 “p”,或者代表换行的 “br”。HTMLElement还定义了一个可选属性text,用来设置HTML元素呈现的文本。除了上面的两个属性,HTMLElement还定义了一个lazy属性asHTML。这个属性引用了一个将name和text组合成HTML字符串片段的闭包。该属性是Void -> String类型,或者可以理解为 “一个没有参数,返回String的函数”。默认情况下,闭包赋值给了asHTML属性,这个闭包返回一个代表HTML标签的字符串。如果text值存在,该标签就包含可选值text;如果text不存在,该标签就不包含文本。对于段落元素,根据text是 “some text” 还是nil,闭包会返回 some text"
或者""
可以像实例方法那样去命名,使用asHTML属性。然而,由于 asHTML 是闭包而不是实例方法,如果你想改变特定HTML元素的处理方式的话,可以用自定义的闭包来取代默认值。例如,可以将一个闭包赋值给 asHTML 属性,这个闭包能在 text 属性是 nil 时使用默认文本,这是为了避免返回 一个空的 HTML 标签:
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)\(heading.name)>"
}
print(heading.asHTML())
// Prints "some default text
"
不幸的是,上面写的 HTMLElement 类产生了类实例和作为 asHTML 默认值的闭包之间的循环强引用。循环强引用如 下图所示:
实例的asHTML属性给它的闭包保留了强引用,然而,因为闭包在闭包体里引用self(引用了self.name和self.text),闭包捕获self,这就意味着闭包反过来保留了HTMLElement实例的强引用,在这两个对象直降一个强引用就此产生了。更多在闭包内捕获值详见捕获值。
如果我们此时将paragraph变量设置为nil且将会断来给HTMLElement实例的强引用。HTMLElement实例和其闭包都不会被释放掉的,因为强引用循环的形成。
paragraph = nil
需要注意的是HTMLElement
的析构器中的信息并不会打印并输出。也就意味着HTMLElement实例没有被释放。