Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two weak variables referencing each other in Swift?

I'm making another attempt today to try to understand retain cycles and weak references in Swift. Reading through the documentation, I saw the following code example where one of the referencing variables is marked as weak to prevent a retain cycle:

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"

Is there any problem with making both variable weak? That is, in the Person class, could I change the apartment variable to be weak so that I have

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

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

where there are two weak variables that reference each other.

I tested it out in the Playground and it seems to work ok, but is there any strong reason not to do this? It seems like in this case there is no natural parent-child relationship here.

like image 326
Suragch Avatar asked Jan 02 '16 12:01

Suragch


People also ask

How do you make a weak reference in Swift?

In Swift, strong references are the default, so to make a reference weak you can use the weak keyword. Unlike strong references, a weak reference does not affect an instance's retain count. It does not hold onto the object.

What is weak reference in Swift?

Swift Weak Reference As mentioned earlier, a weak reference doesn't protect the object from being deallocated. This is because when we declare a property as weak, the reference count of that property will never be more than 1. class Employee { weak var colleague: Employee? ... }

How do you break a strong reference in Swift?

A strong reference cycle happens when 2 instances keep a strong reference to each other. You can accidentally create such a cyclic reference, for example when working with 2-way “links” between objects, or with closures. You can break the cycle by marking a reference as weak, or by setting one of the references to nil.

What is the difference between weak and unowned in Swift?

Like a weak reference, an unowned reference does not increment or decrement the reference count of an object. However, unlike a weak reference, the program guarantees to the Swift compiler that an unowned reference will not be nil when it is accessed.


2 Answers

You can do that. The only side effect is that you need to ensure that something else is retaining the people and the apartments. In the original code you just need to retain the people and the apartments (associated with people) will be retained for you.

Strictly speaking the people aren't killed when the apartments are demolished and the apartments aren't demolished when the people die so weak references in this scenario make sense. It's generally better to consider the relationship and ownership model you want and then decide how to achieve that.

like image 85
Wain Avatar answered Sep 28 '22 03:09

Wain


To augment the accepted answer, here is a concrete example which demonstrates the behavior.

Try this is a 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()

At the time of init in class Test, the apartment that has been created is retained by the local variable unit2B. When init is finished, the apartment will be deallocated because there are no longer any strong references holding it, so the program crashes when test is called because person.apartment is now nil.

If you remove the weak from weak var apartment in class Person, then this example won't crash because the apartment created in init is retained by the person who is retained by the class property person.

The other way to fix the example is to make unit2B be a property of class Test. Then the apartment would have a strong reference holding it so unit2B wouldn't be deallocated after init.

If you remove weak from both weak var apartment in class Person and from weak var tenant in class Apartment, then the example won't crash, but neither the Person nor the Apartment will be deallocated because of the retain cycle created by two objects holding strong references to each other.

like image 32
vacawama Avatar answered Sep 28 '22 03:09

vacawama