惰性初始化和保留周期
Lazy initialisation and retain cycle
使用惰性初始化器时,是否有可能出现循环保留?
在 blog post 和许多其他地方 [unowned self]
可见
class Person {
var name: String
lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
我试过了
class Person {
var name: String
lazy var personalizedGreeting: String = {
//[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
print("person init")
self.name = name
}
deinit {
print("person deinit")
}
}
像这样使用
//...
let person = Person(name: "name")
print(person.personalizedGreeting)
//..
并且发现 "person deinit" 被记录了。
所以似乎没有保留循环。
据我所知,当一个块捕获 self 时,当这个块被 self 强烈保留时,就会有一个保留周期。这种情况看似类似于retain cycle,其实不然。
在这种情况下,您不需要捕获列表,因为在实例化 personalizedGreeting
之后没有引用 self
。
正如 MartinR 在他的评论中所写,您可以通过在删除捕获列表时记录 Person
对象是否被取消初始化来轻松检验您的假设。
例如
class Person {
var name: String
lazy var personalizedGreeting: String = {
_ in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
deinit { print("deinitialized!") }
}
func foo() {
let p = Person(name: "Foo")
print(p.personalizedGreeting) // Hello Foo!
}
foo() // deinitialized!
显然在这种情况下不存在强引用循环的风险,因此在惰性闭包中不需要 unowned self
的捕获列表。这样做的原因是懒惰闭包只执行一次,并且只使用闭包的return值来(懒惰地)实例化personalizedGreeting
,而在这种情况下,对 self
的引用不会超过闭包的执行。
但是,如果我们要在 Person
的 class 属性 中存储类似的闭包,我们将创建一个强引用循环,作为 属性 的self
将保持对 self
的强引用。例如:
class Person {
var name: String
var personalizedGreeting: (() -> String)?
init(name: String) {
self.name = name
personalizedGreeting = {
() -> String in return "Hello, \(self.name)!"
}
}
deinit { print("deinitialized!") }
}
func foo() {
let p = Person(name: "Foo")
}
foo() // ... nothing : strong reference cycle
假设:惰性实例化闭包自动将 self
捕获为 weak
(或 unowned
),默认情况下
当我们考虑下面的例子时,我们意识到这个假设是错误的。
/* Test 1: execute lazy instantiation closure */
class Bar {
var foo: Foo? = nil
}
class Foo {
let bar = Bar()
lazy var dummy: String = {
_ in
print("executed")
self.bar.foo = self
/* if self is captured as strong, the deinit
will never be reached, given that this
closure is executed */
return "dummy"
}()
deinit { print("deinitialized!") }
}
func foo() {
let f = Foo()
// Test 1: execute closure
print(f.dummy) // executed, dummy
}
foo() // ... nothing: strong reference cycle
即foo()
中的f
没有被反初始化,鉴于这个强引用循环,我们可以得出结论,self
在惰性变量的实例化闭包中被强捕获dummy
.
我们还可以看到,在我们从未实例化 dummy
的情况下,我们永远不会创建强引用循环,这将支持最多一次延迟实例化闭包可以被视为运行时范围(很像 never reached if) 要么 a) never reached (non-initialized) 要么 b) reached, fully executed and "thrown away" (end of scope).
/* Test 2: don't execute lazy instantiation closure */
class Bar {
var foo: Foo? = nil
}
class Foo {
let bar = Bar()
lazy var dummy: String = {
_ in
print("executed")
self.bar.foo = self
return "dummy"
}()
deinit { print("deinitialized!") }
}
func foo() {
let p = Foo()
// Test 2: don't execute closure
// print(p.dummy)
}
foo() // deinitialized!
有关强引用循环的更多阅读,请参阅
I tried this [...]
lazy var personalizedGreeting: String = { return self.name }()
it seems there are no retain cycles
正确。
原因是立即应用的闭包 {}()
被认为是 @noescape
。它不保留捕获的 self
.
供参考:Joe Groff's tweet.
在我的洋葱中,事情可能是这样的。该块肯定会捕获自引用。但是记住,如果做一个retain cycle,前提是self必须retain block。但如您所见,惰性 属性 仅保留块的 return 值。所以,如果惰性 属性 没有被初始化,那么外部上下文会保留惰性块,使其包含并且没有完成循环。但是有一件事我还是不清楚,就是惰性块何时被释放。如果 lazy 属性 被初始化,很明显 lazy 块很快就会被执行和释放,也没有 retain cycle 完成。我认为主要问题在于谁保留惰性块。如果在块内捕获 self 时没有完成保留周期,则它可能不是 self。至于@noescape,我不这么认为。 @noescape 不是不捕获,而是暂时存在,任何对象都不应该在这个块上有持久引用,或者换句话说,保留这个块。无法异步使用该块 。如果@noescape 是事实,惰性块如何持续到惰性 属性 被初始化?
使用惰性初始化器时,是否有可能出现循环保留?
在 blog post 和许多其他地方 [unowned self]
可见
class Person {
var name: String
lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
我试过了
class Person {
var name: String
lazy var personalizedGreeting: String = {
//[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
print("person init")
self.name = name
}
deinit {
print("person deinit")
}
}
像这样使用
//...
let person = Person(name: "name")
print(person.personalizedGreeting)
//..
并且发现 "person deinit" 被记录了。
所以似乎没有保留循环。 据我所知,当一个块捕获 self 时,当这个块被 self 强烈保留时,就会有一个保留周期。这种情况看似类似于retain cycle,其实不然。
在这种情况下,您不需要捕获列表,因为在实例化 personalizedGreeting
之后没有引用 self
。
正如 MartinR 在他的评论中所写,您可以通过在删除捕获列表时记录 Person
对象是否被取消初始化来轻松检验您的假设。
例如
class Person {
var name: String
lazy var personalizedGreeting: String = {
_ in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
deinit { print("deinitialized!") }
}
func foo() {
let p = Person(name: "Foo")
print(p.personalizedGreeting) // Hello Foo!
}
foo() // deinitialized!
显然在这种情况下不存在强引用循环的风险,因此在惰性闭包中不需要 unowned self
的捕获列表。这样做的原因是懒惰闭包只执行一次,并且只使用闭包的return值来(懒惰地)实例化personalizedGreeting
,而在这种情况下,对 self
的引用不会超过闭包的执行。
但是,如果我们要在 Person
的 class 属性 中存储类似的闭包,我们将创建一个强引用循环,作为 属性 的self
将保持对 self
的强引用。例如:
class Person {
var name: String
var personalizedGreeting: (() -> String)?
init(name: String) {
self.name = name
personalizedGreeting = {
() -> String in return "Hello, \(self.name)!"
}
}
deinit { print("deinitialized!") }
}
func foo() {
let p = Person(name: "Foo")
}
foo() // ... nothing : strong reference cycle
假设:惰性实例化闭包自动将 self
捕获为 weak
(或 unowned
),默认情况下
当我们考虑下面的例子时,我们意识到这个假设是错误的。
/* Test 1: execute lazy instantiation closure */
class Bar {
var foo: Foo? = nil
}
class Foo {
let bar = Bar()
lazy var dummy: String = {
_ in
print("executed")
self.bar.foo = self
/* if self is captured as strong, the deinit
will never be reached, given that this
closure is executed */
return "dummy"
}()
deinit { print("deinitialized!") }
}
func foo() {
let f = Foo()
// Test 1: execute closure
print(f.dummy) // executed, dummy
}
foo() // ... nothing: strong reference cycle
即foo()
中的f
没有被反初始化,鉴于这个强引用循环,我们可以得出结论,self
在惰性变量的实例化闭包中被强捕获dummy
.
我们还可以看到,在我们从未实例化 dummy
的情况下,我们永远不会创建强引用循环,这将支持最多一次延迟实例化闭包可以被视为运行时范围(很像 never reached if) 要么 a) never reached (non-initialized) 要么 b) reached, fully executed and "thrown away" (end of scope).
/* Test 2: don't execute lazy instantiation closure */
class Bar {
var foo: Foo? = nil
}
class Foo {
let bar = Bar()
lazy var dummy: String = {
_ in
print("executed")
self.bar.foo = self
return "dummy"
}()
deinit { print("deinitialized!") }
}
func foo() {
let p = Foo()
// Test 2: don't execute closure
// print(p.dummy)
}
foo() // deinitialized!
有关强引用循环的更多阅读,请参阅
I tried this [...]
lazy var personalizedGreeting: String = { return self.name }()
it seems there are no retain cycles
正确。
原因是立即应用的闭包 {}()
被认为是 @noescape
。它不保留捕获的 self
.
供参考:Joe Groff's tweet.
在我的洋葱中,事情可能是这样的。该块肯定会捕获自引用。但是记住,如果做一个retain cycle,前提是self必须retain block。但如您所见,惰性 属性 仅保留块的 return 值。所以,如果惰性 属性 没有被初始化,那么外部上下文会保留惰性块,使其包含并且没有完成循环。但是有一件事我还是不清楚,就是惰性块何时被释放。如果 lazy 属性 被初始化,很明显 lazy 块很快就会被执行和释放,也没有 retain cycle 完成。我认为主要问题在于谁保留惰性块。如果在块内捕获 self 时没有完成保留周期,则它可能不是 self。至于@noescape,我不这么认为。 @noescape 不是不捕获,而是暂时存在,任何对象都不应该在这个块上有持久引用,或者换句话说,保留这个块。无法异步使用该块