Search code examples
swiftclosuresreference-cycle

Why does adding a closure capture list prevent my instance from being deallocated?


class Name {
    var name: String
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) deinit")
    }
}
var x: Name? = Name(name: "abc")

var someClosure = {
    print("\(x?.name)")
}

someClosure()

x = nil

And then the console will output:

Optional("abc")
abc deinit

It can be seen that the "deinit" function was called. So it does not form a strong reference cycle. But if I add a capture list to the closure:

var someClosure = { [x] in
    print("\(x?.name)")
}

The console will output:

Optional("abc")

And the "deinit" function was not called. So the object and reference form a strong reference cycle.

What is the reason? What's the difference between these two conditions?


Solution

  • First of all, there's no strong retain cycle in either case – you just simply have a global closure variable that's holding a strong reference to your class instance, therefore preventing it from being deallocated.

    In your first example, when you capture x in the closure:

    var someClosure = {
        print("\(x?.name)")
    }
    

    What you've got (in effect) is a reference to a reference – that is, the closure has a reference to the storage of x, which then has a reference to your class instance. When you set x to nil – the closure still has a reference to the storage of x, but now x doesn't have a reference to your class instance. Thus, your class instance no longer has any strong references to it, and can be deallocated.

    In your second example when you use a capture list:

    var someClosure = { [x] in
        print("\(x?.name)")
    }
    

    You're copying x itself – that is, you're copying a reference to the instance of your class. Therefore the closure will keep your class retained as long as it exists. Setting x to nil doesn't affect the closure's reference to your instance, as it has it's own strong reference to it.