Reputation: 3449
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?
Upvotes: 7
Views: 929
Reputation: 13679
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.
Upvotes: 6
Reputation: 63137
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.
No, you have one strong pointer, from the closure to self
. There's no cyclic reference back from the closure to self
. Thus, you have a directed acylic graph, which is no problem for ARC.
However, your experiment is flawed, from the get-go. Even if the closure was captured, the John Appleseed
Person
object would still deinit
. This object's lifecycle is exclusively dependent on on the john
reference from your ViewController
. When you set that reference to nil
, you're removing the last reference to the John Appleseed
object, thus it's deinitialized.
Upvotes: 0
Reputation: 3147
You're capturing self
which points to ViewController
, but you're wondering about the Person
instance.
Person
is actually not circular referenced and therefore gets de-initalized and released just fine when you set it to nil at the end of your closure.
Implement deinit
for ViewController
and see how that works.
Upvotes: 0