1

I experimented it with the following code in Xcode Playground:

class X {

    var a = 3

    init(a: Int) {
        self.a = a
    }

    deinit {
        print("\(self.a) is deallocated.")
    }

    func returnX() -> Int {
        return self.a
    }

    lazy var anotherReturnX: () -> Int = {
        return self.a
    }
}

var e: X? = X(a: 6)
print(e!.returnX())
e = nil // prints "6 is deallocated."

var f: X? = X(a: 7)
print(f!.anotherReturnX())
f = nil // prints nothing

From the above code, I can see that no reference is captured in the function returnX(), thus e is deallocated once I set e to nil. However, a reference is captured in the closure anotherReturnX(), thus f is not deallocated. Apparently, this implies that a closure captures references while a function does not.

Additionally, when I first type out the code, I didn't include lazy keyword before the closure declaration, as I thought it would be unnecessary to do so. However it triggers a compile-time error. I infer that since the closure can only be accessed after instantiation, it must access to the instantiated self. But since what I am declaring here is effectively an "anonymous function", why would the closure access to self during instantiation anyway?

After putting in some thoughts, I found more contradictions. For instance, I understand that a reference is captured when a closure is called. However, during initialisation of X, I am simply assigning a closure to a variable without calling it, same as the declaration of other instance properties. Thus the closure should not do anything during initialisation, and compiling the code without keyword lazy should be fine. But compilation fails. I am not sure what goes wrong in my understanding.

I have read some related articles, such as strong/weak reference, retain cycle, lazy stored property. However, many explain "what happens" and do not say much about "why".

So other than the question raised in the title, I would also like to clarify, what makes function and closure different from each other such that the above situation happens?

Update:

The fact that closure captures reference while function does not is further "enforced" on me, since, according to the Xcode Compiler, I can rewrite return self.a as return a in returnX(), but I cannot do so in anotherReturnX. As such, I guess I would have to accept that, although function and closure are similar because each of them is "a bundle of functionalities", function is different from closure that it does not capture references. If I were to go deeper in the reason behind this, it would probably involve the design of Swift itself?

However, I still cannot understand why lazy keyword is required for closure declaration.

qsmy
  • 383
  • 3
  • 14

2 Answers2

1
lazy var anotherReturnX: () -> Int = {
    return self.a
}

The self here is a strong self. When an object references another object strongly, ARC cant deallocate so a retain cycle is created. The reference should be weak to avoid a retain cycle by creating weak self inside the block.

lazy var anotherReturnX: () -> Int = { [weak self] in
    return self?.a
}
0

returnX is a method of the class. Methods don't capture variables. self is an implicit local variable in methods, that is implicitly passed to the method when the method is called.

anotherReturnX is a property that is assigned a closure lazily. That closure captures outside variables that are used within it, including self. That capturing creates a strong reference from the closure to the X instance, which, combined with the strong reference from the X instance to the closure, creates a retain cycle.

newacct
  • 119,665
  • 29
  • 163
  • 224