为什么 Swift ARC 不首先中断 属性 引用以防止保留循环?

Why doesn't Swift ARC break property references first to prevent retain cycles?

我只是想知道当一段代码超出范围时 ARC 是如何工作的。我假设所有引用变量都设置为 nil/destroyed 并排除了属性,并且所有引用计数为 0 的对象都被销毁了。 这种情况:

A = nil
B = nil

或者也许所有变量和 属性 引用都从上到下设置为 nil,这将保留任何保留周期,因为在访问属性之前 A 和 B 为 nil。

A = nil
B = nil
A?.macbook = nil
B?.person = nil

所以我只是想知道为什么 ARC 不首先循环遍历属性(向后遍历代码)以首先删除 属性 引用,这会破坏任何保留循环。

    A?.macbook = nil
    B?.person = nil
    A = nil
    B = nil

我想我了解保留周期的基础知识,我只是想知道在自动销毁阶段 ARC 发生的细节,例如完成一个函数的执行。也许属性引用不是 accessed/destroyed 因为它们是对象的一部分?也许计算上太复杂了,无法检查每个 属性?

       var name: String
       init(name: String) {
           self.name = name
           print("The Person \(name) is born")
       }
       var macbook: Macbook?
       deinit {
           print("The Person \(name) has died")
       }
   }
   
   class Macbook {
       var model: String
       init(model: String) {
           self.model = model
           print("The \(model) Macbook is born")
       }
       var person: Person?
       deinit {
           print("The \(model) Macbook has expired")
       }
   }
   
   func runTasks() {
       var A: Person? = Person(name: "Reuben")
       var B: Macbook? = Macbook(model: "Pro 2020")
       
       //Setup Retain Cycle
       A?.macbook = B
       B?.person = A
       
       //A?.macbook = nil
       B?.person = nil //**Why doesn't ARC make this nil first to avoid retain cycles?**
       A = nil
       B = nil
   }
   
   runTasks()

你问:

I'm just curious about the detail of what happens with ARC during an automated destruction phase e.g. finishing the execution of a function.

当您有一个引用对象实例的局部变量时,它会建立对该对象的强引用。当局部变量超出范围时,它会释放其强引用。如果这是对该对象的最后一个强引用,该对象将被释放。

func runTasks() {
    var person = Person(name: "Reuben")
    
    // At this point, the `Person` object has one strong reference, as does the `MacBook`

    doSomething(with: person)

    // The `person` variable falls out of scope, the sole strong reference to the `Person` 
    // instance will be relieved, and the `Person` object will therefore be deallocated.
}

Perhaps properties references aren't accessed/destroyed because they are part of the object?

当一个对象被释放时,它的任何具有对其他对象的强引用的属性也会自动释放

func runTasks() {
    var person = Person(name: "Reuben")
    var computer = Macbook(model: "Pro 2020")
    
    // At this point, the `Person` object has one strong reference, as does the `MacBook`

    person.macbook = computer

    // Now the `Macbook` instance has two strong references, the local variable and the `Person`

    doSomething(with: person)

    // When `person` and `computer` variables the local variables fall out of scope
    // at the end of this function, their respective strong references to the `Person` 
    // and the `Macbook` objects will be released. But since there are no more strong
    // references to `Person`, it will be deallocated. But when it is deallocated,
    // its strong reference to `Macbook` will be released automatically, too. So now,
    // the two strong references to that `Macbook` object are now released, too (both
    // the `computer` local variable and the `macbook` property of `Person`), so it will
    // deallocated, too.
}

现在,正如您所确定的那样,强引用循环(以前称为“保留循环”)是指两个或多个对象彼此保持强引用(因此,除非您手动 nil或更多这些引用,它们最终将以相互挥之不去的强引用而告终,因此在循环被打破之前,它们都不会被释放。

内存管理系统在运行时为我们识别和打破这些强引用循环似乎很有吸引力,但这在计算上是不切实际的。它必须构建一个内存图,识别哪些对象相互引用,并以某种方式找出要中断的引用。甚至在某些情况下,我们可能会有一些我们绝对不希望 OS 为我们解决的短暂周期(例如,您创建一个调度队列,调度一些工作块,我们依靠队列来在分派的块完成之前不会被释放;URLSession 是另一个例子)。

幸运的是,虽然这种常量内存图分析在运行时不可行,Xcode 确实提供了一个调试工具来帮助识别这些场景,即“调试内存图”feature. For more information, see or How can identify strong reference cycles in Swift? or iOS app with ARC, find who is owner of an object .

幸运的是,虽然我们有很好的诊断工具来找到这些强引用循环,但首先要防止它们很容易,只需用 weakunowned 引用打破循环。结果是一个高性能、非常简单的内存管理基础架构,使用 weak/unowned 可以轻松避免循环,但我们有一些出色的调试工具可以在需要时识别问题。