【发布时间】:2020-03-08 12:11:15
【问题描述】:
我想在 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
或者我可以将asHTML 默认初始化更改为包括:
{ [ weak self] in
guard let self = self else { return "-from-the-dead-" }
...
}
也做正确的事。但是我认为可行的方法似乎行不通。我的许多闭包通常看起来像
{ self.doSomething() }
其中doSomething 基本上与闭包签名具有相同的签名。在这种情况下,似乎闭包只是一个额外的包装器。想象一下,如果我向HTMLElement 添加一个具有() -> String 签名的方法:
extension HTMLElement {
func defaultHTML() -> String {
return "\(self.name) = \(self.text ?? "(there is no text)")"
}
}
然后将asHTML 属性直接设置为:
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
出于某种原因,我认为这不会造成循环。但是我好像错了? element 不是 deinit。这有点违反直觉,因为我没有使用大括号来进行闭包。但似乎引用一个活动实例的方法正是这样做的?这是怎么回事?假设没有其他解释,在这种情况下有没有办法打破循环?或者我是否总是需要通过以下方式明确我的闭包:
element.asHTML = { [weak element] in element?.defaultHTML() ?? "-yo-text-be-gone-" }
【问题讨论】:
-
方法和闭包(在大多数意义上)是同一件事,因此当您将函数存储为变量时,您正在捕获 self.defaultHTML(就像您引用类级属性一样) 并在 ARC 中创建另一个引用。这也让我头疼,我通常可以用两种方式说服自己,也必须进行 deinit 测试:-)。我喜欢这个作为参考:avanderlee.com/swift/weak-self
-
“所以我从操场开始” 不是手头的问题,但一般来说,不要那样做。 Playgrounds 会出于可视化目的引用您的对象。它们不是 ARC 行为的可靠描述。
标签: swift memory-management closures