This question was driving me crazy for a few days now and nobody could yet give me a clear view on what's actually happening. Here is the first snippet of the code
class Animal {
var name = "Fischer"
var command: () -> Void = { }
deinit {
print(#function, #line)
}
}
do {
var pet: Animal? = Animal()
pet?.command = { print(pet?.name ?? "Bobby") }
}
This code causes a memory leak, because
class Animal {
var name = "Fischer"
var command: () -> Void = { }
deinit {
print(#function, #line)
}
}
do {
var pet: Animal? = Animal()
pet?.command = { print(pet?.name ?? "Bobby") }
pet = nil
}
And boom! deinit is called, meaning that the object was deallocated, but how? Why was the object deallocated? If we are deleting the exact same reference, that was deleted by the end of the 'do' scope in the first snippet? Am I misunderstanding something? I've been struggling to understand this for a lot of time yet I still don't get how it works.
Your reasoning would be correct if the closure were
{ [pet] in print(pet?.name ?? "Bobby") }
The above closure indeed has a strong reference to the Animal
object, and the Animal
object has a strong reference to the closure, creating a retain cycle.
However, your closure captures the variable pet
. It has a strong reference to whatever object the variable pet
refers to. If pet
is set to nil, then the closure will not have a strong reference to the object that pet
previously refers to.
Compare:
do {
var pet: Animal? = Animal()
// this closure captures the variable 'pet', so 'pet = nil' later will affect what this closure does
pet?.command = { print(pet?.name ?? "Bobby") }
let pet2 = pet
// the 'pet' in the closure now becomes nil
pet = nil
pet2?.command() // this prints "Bobby"
}
do {
var pet: Animal? = Animal()
// this closure captures the object referred to by 'pet' at this moment
pet?.command = { [pet] in print(pet?.name ?? "Bobby") }
let pet2 = pet
// this does not change the object captured by the closure
pet = nil
pet2?.command() // this prints "Fischer"
}
Even after the do
block is done, the variable pet
is not "deleted" - it is held in the closure. Since it is captured by a closure, it gets put on the heap, and does not stay on the stack.