I wanted to experiment with closure cycles some more. So I started with the 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
As expected, no hint of deinit because of the cycle. I can break the cycle if I just initialize asHTML to be a closure that doesn't capture self. E.g.
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
OR I can change the asHTML default initialization to include:
{ [ weak self] in
guard let self = self else { return "-from-the-dead-" }
...
}
Also does the right thing. But an approach that I thought would work, does not seem to. Many of my closures often just look like
{ self.doSomething() }
where doSomething basically has the same signature as the closure signature. In that case, it seems that the closure is just an extra wrapper. Imagine if I add a method to HTMLElement which has the () -> String signature:
extension HTMLElement {
func defaultHTML() -> String {
return "\(self.name) = \(self.text ?? "(there is no text)")"
}
}
and then set the asHTML property directly to that:
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
For some reason, I though that this would not create a cycle. But I appear to be wrong? element does NOT deinit. It's a bit counterintuitive because I didn't use braces to make a closure. But it appears to that referencing a method of a live instance does exactly that? Is that what is going on? Assuming that there's not another explanation, is there a way to break the cycle in this case? Or do I always need to be explicit about my closures with something like:
element.asHTML = { [weak element] in element?.defaultHTML() ?? "-yo-text-be-gone-" }