问题
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