Reputation: 43
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?
Upvotes: 3
Views: 700
Reputation: 80801
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.
Upvotes: 2
Reputation: 755
This is what I think happens here.
ARC adds a reference count to x
when x
is created. When the closure is called with a capture list, it then adds another count (closure captures the object: thus indicating to the compiler it will need x
in the future, by increasing the reference count). So x
now has ARC count of 2.
When you assign x
to nil
it reduces the reference count by 1. If the count is 0, or there are only weak references to the object, then the object is deinitialized. If the count is more than 0, and the references are strong, the object is retained.
Without explicitly passing the capture list to the closure it only weakly captures the object. ARC then decides it is safe to deinit the object once the closure is done with it. Passing the capture list indicates to ARC that the object might be needed for the closure to operate on at various multiple times, so a strong reference is made.
Put another call to someClosure()
after x = nil
and see what happens in both cases.
Upvotes: 0