Lazy initialisation and retain cycle

In this case, you need no capture list as no reference self is pertained after instantiation of personalizedGreeting.

As MartinR writes in his comment, you can easily test out your hypothesis by logging whether a Person object is deinitilized or not when you remove the capture list.

E.g.

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!

It is apparent that there is no risk of a strong reference cycle in this case, and hence, no need for the capture list of unowned self in the lazy closure. The reason for this is that the lazy closure only only executes once, and only use the return value of the closure to (lazily) instantiate personalizedGreeting, whereas the reference to self does not, in this case, outlive the execution of the closure.

If we were to store a similar closure in a class property of Person, however, we would create a strong reference cycle, as a property of self would keep a strong reference back to self. E.g.:

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 instantiating closures automatically captures self as weak (or unowned), by default

As we consider the following example, we realize that this hypothesis is wrong.

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

I.e., f in foo() is not deinitialized, and given this strong reference cycle we can draw the conclusion that self is captured strongly in the instantiating closure of the lazy variable dummy.

We can also see that we never create the strong reference cycle in case we never instantiate dummy, which would support that the at-most-once lazy instantiating closure can be seen as a runtime-scope (much like a never reached if) that is either a) never reached (non-initialized) or 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!

For additional reading on strong reference cycles, see e.g.

  • "Weak, strong, snowned, oh my!" - A guide to references in Swift

I tried this [...]

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

it seems there are no retain cycles

Correct.

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

For reference: Joe Groff's tweet.