问题
I am reading Apple's Swift Programming Language Guide. In the part about Strong Reference Cycle for closures, I tried a different type of closure but it did not give the expected output.
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 {
println("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML)
paragraph = nil
The output of this code is :
<p>hello, world</p>
p is being deinitialized
"p is being deinitialised" is printed even without [unowned self]
The code in the guide is:
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 {
println("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML)
paragraph = nil
This prints the deinitialiser message only when the [unowned self] statement is added.
What is the difference between both the closures?
回答1:
Great question! To explore it, we should simplify the test case to the minimum required to demonstrate it:
// First case, a lazy string
class LazyVar {
let name = "LazyVar"
lazy var lazyName : String = { return self.name }()
deinit {
println("\(name) is being deinitialized")
}
}
var lazyVar: LazyVar? = LazyVar()
println(lazyVar?.lazyName)
lazyVar = nil // Calls deinit
// Second case, a lazy ()->String
class LazyFunc {
let name = "LazyFunc"
lazy var lazyFunc: () -> String = {
// [unowned self] in // <-- This would break the loop
return self.name
}
deinit {
println("\(name) is being deinitialized")
}
}
var lazyFunc: LazyFunc? = LazyFunc()
println(lazyFunc?.lazyFunc())
lazyFunc = nil // Doesn't call deinit
In the first case, there is no permanent retain loop. When you access lazyName, a closure is evaluated { return self.name }. That closure captures self, computes a string, and returns the string. The string is assigned to the property. We're now done with the closure, so it's released, and it releases self. Note that there is a brief retain loop here, but that's ok. It is eventually broken, and that's all that matters.
In the second case, there is a permanent retain loop. When you access lazyFunc(), it calls a closure that creates a new closure. That new closure is {return self.name}, and that is assigned to the property. And that closure retains self, so is a retain loop that is never broken. Adding [unowned self] in to lazyFunc as marked would break the loop for you.
回答2:
In the example from the Swift book, asHTML is a closure:
lazy var asHTML : () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
self holds a strong reference to the closure (via the asHTML
property), and the
closure holds a strong reference to self (unless you add
[unowned self]).
In your example, asHTML is a property of type String:
lazy var asHTML : String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}()
The right-hand side is evaluated once, when the lazy property is accessed
the first time. A closure object is created (capturing self) and evaluated. After that, the closure object is destroyed (because no
strong references exist to it). In particular, the captured reference
to self is released: No closure anymore, no retain cycle.
来源:https://stackoverflow.com/questions/30576146/different-closures-giving-different-results-for-retain-cycles-in-swift