通过显式“nil”赋值打破强引用循环

问题描述 投票:0回答:1

这个问题让我抓狂了好几天,但还没有人能让我清楚地了解到底发生了什么。这是代码的第一个片段

class Animal {
    var name = "Fischer"
    
    var command: () -> Void = { }
    
    deinit {
        print(#function, #line)
    }
}

do {
    var pet: Animal? = Animal()
    pet?.command = { print(pet?.name ?? "Bobby") }
}

这段代码会导致内存泄漏,因为

  1. 已创建引用“宠物”。
  2. 在闭包内创建引用“pet”的独立副本。现在有两个对同一个对象的引用,分别是闭包外部的“pet”和闭包内部的“pet”。
  3. 当我们退出'do'范围时,'pet'引用被删除,但由于强引用'pet',ARC不会释放该对象,它仍然引用同一个对象。 所有这些都会导致内存泄漏。现在这是代码,非常相似,除了我们为“pet”引用分配一个 nil
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
}

繁荣! deinit 被调用,意味着对象被释放,但是如何释放呢?为什么对象被释放?如果我们要删除完全相同的引用,那么该引用是在第一个片段中的“do”范围末尾删除的吗?我误解了什么吗?我很长时间都在努力理解这一点,但我仍然不明白它是如何工作的。

swift memory-leaks null closures strong-reference-cycle
1个回答
0
投票

如果关闭,你的推理是正确的

{ [pet] in print(pet?.name ?? "Bobby") }

上面的闭包确实对

Animal
对象有强引用,而
Animal
对象对闭包也有强引用,从而创建了一个retain循环。

但是,您的闭包捕获了 变量

pet
。它对变量
pet
引用的任何对象都有强引用。如果
pet
设置为 nil,则闭包不会对
pet
之前引用的对象有强引用。

比较:

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"
}

即使在

do
块完成之后,变量
pet
也不会被“删除”——它被保存在闭包中。由于它是被闭包捕获的,因此它被放在堆上,而不是留在堆栈上。

© www.soinside.com 2019 - 2024. All rights reserved.