遅延初期化子を使用しているときに、保持サイクルが発生する可能性はありますか?
ブログ投稿 と他の多くの場所[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」がログに記録されていることがわかりました。
したがって、保持サイクルはないようです。ブロックが自己をキャプチャするとき、およびこのブロックが自己によって強く保持されるときの私の知識によれば、保持サイクルがあります。このケースは保持サイクルに似ていますが、実際はそうではありません。
私はこれを試しました[...]
_lazy var personalizedGreeting: String = { return self.name }()
_
保持サイクルがないようです
正しい。
その理由は、即座に適用されたクロージャ{}()
が_@noescape
_と見なされるためです。キャプチャされたself
は保持されません。
参考: Joe Groffのツイート 。
self
のインスタンス化後に参照personalizedGreeting
が関係しないため、キャプチャリストは必要ありません。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
_のキャプチャリストは必要ありません。この理由は、遅延クロージャはonceのみを実行し、クロージャの戻り値のみを使用して(ゆっくりと)personalizedGreeting
をインスタンス化するためです。この場合、self
への参照は、クロージャーの実行よりも長く存続しません。
ただし、Person
のクラスプロパティに同様のクロージャーを格納する場合、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
をインスタンス化しない場合は、強い参照サイクルを作成しないこともわかります。これにより、最大で1回の遅延インスタンス化クロージャをランタイムスコープと見なすことができます(到達しない場合と同様)。 a)到達しなかった(初期化されていない)、またはb)到達し、完全に実行され、「破棄された」(スコープの終わり)。
_/* 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!
_
強参照サイクルの詳細については、例を参照してください。