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

时间:2016-01-02 12:15:09

标签: swift automatic-ref-counting weak-references

我今天再次尝试了解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类中,我可以将apartment变量更改为弱,以便我有

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

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

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

我在游乐场测试了它似乎工作正常,但有没有强有力的理由不这样做?在这种情况下,似乎没有自然的亲子关系。

3 个答案:

答案 0 :(得分:14)

你可以做到这一点。唯一的副作用是你需要确保其他东西保留了人和公寓。在原始代码中,您只需要保留人员,公寓(与人相关)将保留给您。

严格地说,当公寓被拆除时人们没有被杀死,当人们死亡时公寓没有被拆除,因此这种情况下的弱参考是有道理的。通常最好考虑您想要的关系和所有权模型,然后决定如何实现这一目标。

答案 1 :(得分:5)

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

试试这是一个游乐场:

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()

在课程initTest时,已创建的公寓由本地变量unit2B保留。当init完成后,公寓将被取消分配,因为不再有任何强引用,因此在调用test时程序崩溃,因为person.apartment现在是nil

如果您从weak中的weak var apartment移除class Person,则此示例不会崩溃,因为init中创建的公寓由person保留由类属性person保留的人。

修复示例的另一种方法是将unit2B设为class Test的属性。然后公寓会有一个强有力的参考,因此unit2B之后init将不会被取消分配。

如果您从weak中的weak var apartmentclass Person中的weak var tenant移除class Apartment,则示例不会崩溃,但{由于两个对象之间保持强引用所创建的保留周期,{1}}也不会释放Person

答案 2 :(得分:4)

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

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

为了让对象继续存在,您需要至少有一个强引用。如果不这样做,它将被解除分配。

弱指针不是拥有引用。

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

修改

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

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

object?.method().

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

非常重要:

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

非常重要:

如果你有2个对象具有强引用,你就创建了一个“保留周期”,除非你将来某个时候忽略其中一个指针,否则这些对象将永远不会被释放。如果您有2个(或更多)对象具有强引用,但您没有对这些对象的任何其他引用,则会导致内存泄漏。