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

13

我今天再次尝试理解在Swift中保留循环和弱引用的概念。阅读文档时,我看到以下代码示例,其中一个引用变量被标记为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?
    // ...
}

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

我在Playground中测试了一下,似乎可以正常工作,但是是否有任何强烈的理由不这样做呢?在这种情况下,似乎没有自然的父子关系。

3个回答

15

你可以这样做。唯一的副作用是你需要确保其他地方正在保存人和公寓。在原始代码中,只需保留人,与人相关的公寓将为您保留。

严格来说,在拆除公寓时,人并没有被杀死,并且当人死亡时,公寓也没有被拆除,因此在这种情况下使用弱引用是有道理的。通常最好考虑您想要的关系和所有权模型,然后决定如何实现它。


thumbs up for the language - JAHelia

6
为了补充已接受的答案,这里有一个具体的示例来演示行为。
在 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()

在类Test的init方法中,已创建的公寓由局部变量unit2B所持有。当init方法完成时,因为没有任何强引用持有该公寓,所以程序会崩溃,当调用test方法时,person.apartment现在是nil。
如果从class Person中的weak var apartment中移除weak,则此示例不会崩溃,因为在init中创建的公寓由保留在类属性person中的person所保留。
修复示例的另一种方法是将unit2B作为class Test的属性。然后,公寓将有一个强引用来持有它,因此在init之后,unit2B不会被释放。
如果从class Person中的weak var apartment和class Apartment中的weak var tenant中移除weak,则示例不会崩溃,但是由于两个对象相互持有强引用而创建的保留周期,既不会dealloc Person也不会dealloc Apartment。

1
很好的概念验证例子!这也表明需要思考哪个引用应该被弱化。如果我从 weak var apartment 中删除 weak var tenant 而不是 weak,仍然会崩溃,因为没有东西保留 apartment - Suragch
1
我将对Test()的创建调用和对t.test()的调用放入一个名为go的函数中,以便您可以看到释放消息。现在,如果您删除两个weak属性,您就可以看到保留周期问题了。 - vacawama

4
你提出的问题没有足够的信息让我们回答它。你需要退后一步,学习iOS内存管理。
核心概念是对象所有权。当你创建一个对象并将指针存储到一个强变量中时,系统会增加该对象的保留计数。当变量超出范围或将nil存储到其中时,系统会减少保留计数。当保留计数降至零时,对象被释放。
为了使对象继续存在,你需要至少有一个强引用指向它。如果没有,它将被释放。
弱指针不是拥有引用。
如果对对象的唯一引用是弱引用,它将被释放,可能会立即释放。弱引用是特殊的;当对象被释放时,编译器会将其归零。这意味着如果你尝试向保存在弱变量中的对象发送消息,你不会崩溃。如果它已经被释放,指针将被更改为nil,并且消息将被简单地忽略。
编辑
正如@vacawama指出的那样,在Objective-C中,向nil对象发送消息是一种方法。(我最近一直在全职为客户使用Objective-C工作,所以最近我的思维方式倾向于这方面。但问题是关于Swift的。)
在Swift中,你使用可选链接和这样的语法:
object?.method().

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

非常重要:

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

同样非常重要:

如果您有两个相互持有强引用的对象,则已创建出“循环引用”,除非以后有时间将这些指针之一设为 nil,否则这些对象永远不会被释放。如果您有两个(或更多)相互持有强引用的对象,但您没有任何对这些对象的其他引用,则可能会导致内存泄漏。


向一个为 nil 的对象发送消息并被忽略是 Objective-C 的概念。在 Swift 中,可以使用 可选链 在调用中实现:myObj?.methodCall() - vacawama
没错。我应该注意到这是一个基于Swift的问题。感谢您指出这一点。 - Duncan C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接