当两个属性都不能为 nil 时,unowned 在分配的内存方面有什么区别?

When both properties can never be nil what difference does unowned make in terms of allocated memory?

对于 swift 编程指南中我添加的带有反初始化器的以下代码,无论是否使用 unowned 关键字,生成的调试打印输出都是相同的。 swift 编程指南说,当引用彼此 class 实例的两个属性永远不会为 nil 时,使用无主和隐式展开的可选值是打破强引用循环的一种方法。如果两个属性都永远不会为零,那么这与强引用循环有何不同?例如,为什么我们在这种特殊情况下费心使用 unowned 关键字,尤其是当调试读数显示内存分配与是否使用 unowned 没有区别时?

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
    deinit {print("\(name) is being deinitialized")}
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
    deinit {print("\(name) is being deinitialized")}
}

var canada = Country(name: "Canada", capitalName: "Ottawa")
print("\(canada.name)'s capital city is called \(canada.capitalCity.name)")
canada.capitalCity = City(name: "Vancouver", country: canada)

调试读数:

Canada's capital city is called Ottawa
Ottawa is being deinitialized

注意:这是在操场上。

您显然是在游乐场或某些环境中查看此内容,您不会让它们超出范围(例如,如果它们是某个对象的属性,请查看当该对象本身时会发生什么,被释放)。但是,出于说明目的,请考虑您的代码的这种排列:

func foo() {
    let canada = Country(name: "Canada", capitalName: "Ottawa")
    print("\(canada.name)'s capital city is called \(canada.capitalCity.name)")
    canada.capitalCity = City(name: "Vancouver", country: canada)
}

foo()

这就像你的例子,但我将这些变量的范围限制在 foo 函数内,所以我应该能够看到在该函数中创建和销毁的对象的完整生命周期范围。

Cityunowned 引用 Country 它将报告:

Canada's capital city is called Ottawa
Ottawa is being deinitialized
Canada is being deinitialized
Vancouver is being deinitialized

如果没有 unowned(也不是 weak),它将报告:

Canada's capital city is called Ottawa
Ottawa is being deinitialized

请注意,没有任何与 "Canada" 或 "Vancouver" 的去初始化相关的打印语句。那是因为在没有 unowned 引用的情况下,您最终会在 "Canada" 和 "Vancouver" 之间形成一个强引用循环,并且它们不会被取消初始化。

因此,您看到 "Ottawa" 被取消初始化这一事实与强引用循环无关。这只是被取消初始化,因为 "Ottawa" 被 "Vancouver" 替换,留下 "Ottawa" 没有更多的强引用并且它被取消初始化。

并且您不应该就原始示例中的 deinit 中缺少 print 陈述的任何证据得出任何结论。您可以在操场上完成此操作,或者它们可以是某个本身尚未被释放的对象的属性。将这些变量放在一个受限范围内,例如我在上面对 foo 函数所做的那样,可以更好地说明这些对象超出范围时的真实生命周期。它向我们展示了不解决我们的强引用循环的结果。

最重要的是,您确实需要 unowned(或 weak)在 City 中引用 Country 来打破强引用循环。这不仅仅是关于这些变量是否可以设置为 nil 的问题,而且还有它们是否有可能超出范围并被取消初始化的问题。