Swift 中相互引用的两个弱变量?

Two weak variables referencing each other in Swift?

我今天再次尝试理解 Swift 中的保留循环和弱引用。通读 documentation,我看到了以下代码示例,其中一个引用变量被标记为 weak 以防止保留循环:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment? 
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?             // <---- This var is marked as 'weak'
    deinit { print("Apartment \(unit) is being deinitialized") }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // prints "John Appleseed is being deinitialized"
unit4A = nil // prints "Apartment 4A is being deinitialized"

让两个变量都变弱有什么问题吗?也就是说,在 Person class 中,我可以将 apartment 变量更改为 weak 以便我有

class Person {
    // ...
    weak var apartment: Apartment?  // added 'weak'
    // ...
}

class Apartment {
    // ...
    weak var tenant: Person?
    // ...
}

其中有两个相互引用的弱变量。

我在 Playground 上测试过它似乎工作正常,但是有什么充分的理由不这样做吗?在这种情况下,这里似乎没有天然的亲子关系。

你可以的。唯一的副作用是您需要确保其他东西能够留住人和公寓。在原来的代码中,你只需要保留人,公寓(与人相关联)就会为你保留。

严格来说,公寓被拆时人并没有被杀,人死时公寓也没有被拆,所以这种情况下的弱引用是有道理的。通常最好先考虑您想要的关系和所有权模式,然后再决定如何实现。

为了扩充已接受的答案,这里有一个具体示例来演示该行为。

试试这是一个游乐场:

class Person {
    let name: String
    init(name: String) { self.name = name }
    weak var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?             // <---- This var is marked as 'weak'
    deinit { print("Apartment \(unit) is being deinitialized") }
}

class Test {
    var person: Person
    init() {
        person = Person(name: "Fred")
        let unit2B = Apartment(unit: "2B")
        person.apartment = unit2B
        unit2B.tenant = person
        print(person.apartment!.unit)
    }

    func test() {
        print(person.apartment!.unit)
    }
}

func go() {
    let t = Test()
    t.test()  // crashes here!
}

go()

在classinitTest,已经创建的公寓由局部变量unit2B保留。当 init 完成时,公寓将被释放,因为不再有任何强引用持有它,所以当调用 test 时程序崩溃,因为 person.apartment 现在是 nil .

如果您从 class Person 中的 weak var apartment 中删除 weak,则此示例不会崩溃,因为 init 中创建的公寓由 person谁被class属性person.

保留

另一种 修复 示例的方法是使 unit2B 成为 class Test 的 属性。然后公寓将有一个强大的引用持有它所以 unit2B 不会在 init.

之后被释放

如果从 class Person 中的 weak var apartmentclass Apartment 中的 weak var tenant 中删除 weak,则示例不会崩溃,但两者都不会PersonApartment 都将被释放,因为两个对象相互持有强引用而创建了保留循环。

您的问题没有提供足够的信息让我们回答。你需要退一步研究iOS内存管理。

核心概念是对象所有权。当您创建一个对象并将指向它的指针存储在强变量中时,系统会增加该对象的保留计数。当变量超出范围或您将 nil 存储到其中时,系统会减少保留计数。当保留计数降为零时,对象将被释放。

为了让一个对象继续存在,你需要至少有一个对它的强引用。如果你不这样做,它将被释放。

弱指针不是拥有引用。

如果对一个对象的唯一引用是弱引用,它将被释放,可能是立即释放。弱引用是特殊的;当对象被释放时,编译器将它们清零。这意味着如果您尝试向保存在弱变量中的对象发送消息,您将不会崩溃。如果它被释放,指针将变为 nil,消息将被忽略。

编辑

正如@vacawama 所指出的,向 nil 对象发送消息是 Objective-C 做事的方式。 (我最近在 Objective-C 为一位客户工作 full-time,所以这往往是我最近的心态。但是问题是关于 Swift。)

在 Swift 中,您使用可选链接代替,语法如下:

object?.method().

使用此 Swift 语法,如果对象为 nil,则跳过方法调用。

非常重要:

如果您有 2 个对象,每个对象都对彼此有弱引用,那很好,但是在您程序的其他地方,您需要对这两个对象有强(拥有)引用,否则它们将被释放。

同样非常重要:

如果你有 2 个对象,它们彼此有强引用,你就创建了一个“保留循环”,除非你在将来的某个时间将其中一个指针置零,否则这些对象将永远不会被释放。如果您有 2 个(或更多)对象彼此具有强引用,但您没有对这些对象的任何其他引用,则您已导致内存泄漏。