Memory leaks caused by closures

Hi there, this is my first time posting here. I've heard that some of the apple developers are usually active on these forums, so I've decided to shoot my shot, because 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

  1. Reference 'pet' is created.

  2. Independent copy of the reference 'pet' is created inside the closure. now there are two references to the same object, which are 'pet' outside the closure and 'pet' inside the closure.

  3. As we exit the 'do' scope, the 'pet' reference is deleted, but ARC does not deallocate the object due to the strong reference 'pet', that is still referencing to the same object.

And all of that causes a memory leak. Now here is the code, that is pretty similar, except for the fact, that we assign a nil to the 'pet' reference

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 really hope this post will find the right people, since I could not even find appropriate tags for that.

Answered by DTS Engineer in 807748022
I've heard that some of the apple developers are usually active on these forums

That’s true. I also want to make sure you’re aware of Swift Forums. If you have questions about Swift the language, rather than some Apple API, that’s a great place for them.

But I’m happy to answer Swift questions here too; it keeps me in practice (-:

Regarding your specific issue, the reason why your second example deallocates immediately when you set pet to nil is that the closure captures the pet variable, not the object it points to. So let’s come back to this:

Independent copy of the reference pet is created inside the closure. now there are two references to the same object, which are 'pet' outside the closure and 'pet' inside the closure.

No, that’s not right. It’d be better to say that:

An independent reference to the pet variable is captured by the closure. There is a single references to the same object stored in the pet variable, but there are multiple references to that pet variable.

Consider this:

func main() {
    var counter = 0
    let c = {
        counter += 1
    }
    print(counter)  // 0
    c()
    print(counter)  // 1
}

main()

Note how the closure has captured a reference to counter. That’s analogous to what’s going on in your example. That is, pet isn’t the object, it’s a variable that references the object, and closures can capture references to such variables.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I've heard that some of the apple developers are usually active on these forums

That’s true. I also want to make sure you’re aware of Swift Forums. If you have questions about Swift the language, rather than some Apple API, that’s a great place for them.

But I’m happy to answer Swift questions here too; it keeps me in practice (-:

Regarding your specific issue, the reason why your second example deallocates immediately when you set pet to nil is that the closure captures the pet variable, not the object it points to. So let’s come back to this:

Independent copy of the reference pet is created inside the closure. now there are two references to the same object, which are 'pet' outside the closure and 'pet' inside the closure.

No, that’s not right. It’d be better to say that:

An independent reference to the pet variable is captured by the closure. There is a single references to the same object stored in the pet variable, but there are multiple references to that pet variable.

Consider this:

func main() {
    var counter = 0
    let c = {
        counter += 1
    }
    print(counter)  // 0
    c()
    print(counter)  // 1
}

main()

Note how the closure has captured a reference to counter. That’s analogous to what’s going on in your example. That is, pet isn’t the object, it’s a variable that references the object, and closures can capture references to such variables.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Deleted

I think this is best illustrated with a capture list. You can capture the object, but it's not the default behavior.

final class Object {
  var closure: () -> Object? = { nil }
}

@Test func test() {
  do {
    var object: Optional = Object()
    object!.closure = { object }
    weak var weakObject = object
    object = nil
    #expect(weakObject == nil)
  }

  do {
    var object: Optional = Object()
    object!.closure = { [object] in object }
    weak var weakObject = object
    object = nil
    #expect(weakObject != nil)
  }
}

Right.

Except you still need to be clear about terminology. You’re not capturing the object per se. Rather, the capture creates its own reference to the object. Your second example kinda behaves like this:

var object: Object? = Object()
let o = object
{ 
    print(o)
}

That is, at the point that the closure is created, the capture list creates a constant copy of the object variable and ties that to the closure.

Your example actually illustrates this because, within your closure, the type of object is not Object but rather Object?. So your capturing an enum, not an object.

Of course this is a subtlety that doesn’t really matter. Your key point about capture lists is spot on.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Memory leaks caused by closures
 
 
Q