我想用Swift中的闭包周期做一些实验。所以我从一个操场和example in the Swift documentation开始。
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")
}
}
var element = HTMLElement(name: "Travis", text: "Griggs")
print(element.asHTML()) // force the cycle
element = HTMLElement(name: "Bat", text: "Man") // encourage first element to deallocate
正如所料,由于周期的原因,没有任何
deinit
的提示。如果我将asHTML
初始化为一个不捕获自身的闭包,我就可以打破这个循环。例如。var element = HTMLElement(name: "Travis", text: "Griggs")
element.asHTML = { "No Cycles Here" }
print(element.asHTML()) // do the print
element = HTMLElement(name: "Bat", text: "Man") // encourage first element to deallocate
或者我可以将默认初始化更改为包括:
{ [ weak self] in
guard let self = self else { return "-from-the-dead-" }
...
}
也做正确的事。但我认为可行的方法,似乎并不奏效。我的很多闭包看起来就像
{ self.doSomething() }
其中
asHTML
基本上与闭包签名具有相同的签名。在这种情况下,闭包似乎只是一个额外的包装器。想象一下,如果我向具有doSomething
签名的HTMLElement
添加一个方法:extension HTMLElement {
func defaultHTML() -> String {
return "\(self.name) = \(self.text ?? "(there is no text)")"
}
}
然后直接将
() -> String
属性设置为:var element = HTMLElement(name: "Travis", text: "Griggs")
element.asHTML = element.defaultHTML
print(element.asHTML())
element = HTMLElement(name: "Bat", text: "Man") // encourage first element to deallocate
出于某种原因,我认为这不会造成一个循环。但我似乎错了?
asHTML
不element
。这有点违反直觉,因为我没有用大括号来结束。但看起来引用一个活动实例的方法确实做到了这一点?这是怎么回事?假设没有其他解释,在这种情况下有没有办法打破这个循环?或者我总是需要明确我的闭包,比如:element.asHTML = { [weak element] in element?.defaultHTML() ?? "-yo-text-be-gone-" }
最佳答案
defaultHTML()
不仅仅是一个函数,它还是一个实例方法。它在一个实例上运行,在您的例子中,self
。您的闭包不能只记住“just calldefaultHTML
”,而不记住要调用哪个实例。
因此,当您创建一个类似于{ self.defaultHTML }
的闭包时,您将创建一个堆分配的ARC-managed框,其中包含对self
的强引用和指向defaultHTML
实例方法实现的指针。