Lazy initialization and saving cycle

When using lazy initializers, is there a chance of saving loops?

In a blog post and elsewhere, [unowned self] displayed

 class Person { var name: String lazy var personalizedGreeting: String = { [unowned self] in return "Hello, \(self.name)!" }() init(name: String) { self.name = name } } 

I tried this

 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") } } 

Used like that

 //... let person = Person(name: "name") print(person.personalizedGreeting) //.. 

And found that "person deinit" was registered.

So it seems that there are no retention cycles. According to my knowledge, when a block captures itself, and when this block is strongly preserved by itself, there is a conservation cycle. This case is similar to a retention cycle, but in fact it is not.

+20
memory-management memory-leaks automatic-ref-counting lazy-initialization swift
source share
2 answers

I tried this [...]

 lazy var personalizedGreeting: String = { return self.name }() 

it seems that there are no retention cycles

Correctly.

The reason is that the immediately applied closure {}() is considered @noescape . It does not save the captured self .

For reference: Joe Groff tweet .

+54
source share

In this case, you do not need a capture list, because the self link is not mentioned after the personalizedGreeting instance.

As MartinR writes in his comment, you can easily test your hypothesis by registering that the Person object is uninitialized or not when you delete the capture list.

eg.

 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! 

Obviously, in this case there is no risk of a strong reference cycle and, therefore, there is no need for an unowned self capture list in lazy closure. The reason for this is that lazy closure is done only once and uses only the return value of the closure (lazy), creating an instance of personalizedGreeting , while the reference to self in this case does not complete the closure.

If we kept a similar closure in the property of the Person class, we would create a strong reference loop, since the self property will retain a strong reference to self . For example:.

 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 

Hypothesis: lazy instantiations automatically unowned self as weak (or unowned ) by default

As we examine the following example, we understand that this hypothesis is false.

 /* 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 

Ie, f in foo() not de-initialized and, given this strong reference loop, we can conclude that self strongly fixed in the closure instance of the lazy dummy variable.

We can also see that we never create a strong reference loop in case we never create a dummy instance that supports the fact that the simplest lazy instance creation can be considered as a execution area (like if never achieved), which is either a ) has never been achieved (not initialized), or b) achieved, fully executed and “thrown out” (end of area).

 /* 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! 

For further reading on strong reference cycles, see for example

+3
source share

All Articles