swiftmemory-leaksclosuresretain-cyclestrong-references

Swift - Expecting a leak after strongly capturing self in closure


Can anyone please explain why this doesn't leak?

I'm capturing self within a closure so I would have two strong pointers pointing at each other, therefore, the deinit message shouldn't ever be called for the Person object.

First, this is my class Person:

class Person {
    var name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}

And this is my ViewController's implementation:

class ViewController: UIViewController {

    var john:Person?

    func callClosureFunction( closure:(name:Bool) -> () ) {
        closure(name: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        john = Person(name:"John")

        self.callClosureFunction { (name) in

            self.john?.name = "John Appleseed"
            self.john = nil

            // xcode prints - John Appleseed is being deinitialized
        }

    }

}

I was expecting to be able to fix the issue by doing:

self.callClosureFunction { [weak self] (name) in ...

But that wasn't even necessary. Why?


Solution

  • Since your view controller is not retaining the closure, there is no circular reference. If you wrote this:

    class ViewController: UIViewController {
    
        var john:Person?
        var closure:(Bool)->()? 
    
        func callClosureFunction( closure:((name:Bool) -> ())? ) {
            closure?(name: true)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            john = Person(name:"John")
            closure = { (name) in
    
                self.john?.name = "John Appleseed"    
    
                // Because this closure will never be released, the instance of Person will never deinit either
            }
            self.callClosureFunction(closure) 
        }  
    }
    

    then the view controller would retain the closure and the closure would retain the view controller via its reference to self. Therefore, neither would be released, and if you don't explicitly set self.john = nil (which you did in your original example), then the Person instance would never get deninit called.

    It's quite common to inappropriately use weak self in closures when not necessary (and this can actually lead to some obscure bugs). The key rule to remember is that weak references are not the default in general under ARC. Strong should be the default unless it would lead to a retain cycle, in which case weak should be used only to break that circular reference. Same for closures: strong self should be the default, unless the self in this case also has a strong reference to the closure itself.