Две слабые переменные, ссылающиеся друг на друга в Свифте?

Я делаю еще одну попытку сегодня попытаться понять циклы сохранения и слабые ссылки в 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?
    // ...
}

где есть две слабые переменные, которые ссылаются друг на друга.

Я тестировал его на игровой площадке, и, похоже, он работает нормально, но есть ли сильная причина не делать этого? Похоже, в этом случае нет никаких отношений между родителями и детьми.

Ответ 1

Вы можете это сделать. Единственным побочным эффектом является то, что вам нужно обеспечить, чтобы что-то еще удерживало людей и квартиры. В исходном коде вам просто нужно сохранить людей, а квартиры (связанные с людьми) будут сохранены для вас.

Строго говоря, люди не убиваются, когда квартиры разрушаются, и квартиры не разрушаются, когда люди умирают, так что слабые ссылки в этом сценарии имеют смысл. Как правило, лучше рассмотреть модель отношений и собственности, которую вы хотите, а затем решить, как это достичь.

Ответ 2

Чтобы увеличить принятый ответ, вот конкретный пример, демонстрирующий поведение.

Попробуйте это игровая площадка:

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

Во время init в классе Test квартира, которая была создана, сохраняется локальной переменной unit2B. Когда init будет завершено, квартира будет освобождена, потому что больше нет сильных ссылок, удерживающих ее, поэтому программа вылетает, когда вызывается Test, потому что person.apartment теперь nil.

Если вы удалите weak из weak var apartment в class Person, этот пример не будет сбой, потому что квартира, созданная в init, сохраняется person, которая сохраняется в свойстве класса person.

Другой способ исправить пример - сделать unit2B свойством class Test. Тогда квартира будет иметь сильную ссылку, удерживая ее так unit2B не будет освобождена после init.

Если вы удалите weak из weak var apartment в class Person и из weak var tenant в class Apartment, то пример не будет аварийно завершен, но ни person, ни Apartment не будут освобождены из-за цикла сохранения, созданного двумя объектами, имеющими сильные ссылки друг на друга.

Ответ 3

Ваш вопрос не дает достаточной информации, чтобы мы могли ответить на него. Вам нужно отступить и изучить управление памятью iOS.

Основной концепцией является владение объектами. Когда вы создаете объект и сохраняете указатель на него в сильной переменной, система увеличивает счетчик сохранения для этого объекта. Когда переменная выходит за пределы области действия или вы храните в нее нуль, система уменьшает количество удержаний. Когда количество удержаний падает до нуля, объект освобождается.

Чтобы объект продолжал жить, вам нужно иметь хотя бы одну сильную ссылку на него. Если вы этого не сделаете, он будет освобожден.

Слабый указатель НЕ является ссылкой на владельца.

Если единственными ссылками на объект являются слабые ссылки, он будет освобожден, возможно, немедленно. Слабые ссылки являются особенными; компилятор завершает их, когда объект освобождается. Это означает, что вы не сработаете, если попытаетесь отправить сообщение объекту, сохраненному в слабой переменной. Если он был освобожден, указатель будет изменен на nil, и сообщение просто проигнорируется.

ИЗМЕНИТЬ

Как указано @vacawama, отправка сообщений в нулевой объект - это способ Objective-C делать вещи. (В последнее время я работаю полный рабочий день для клиента в Objective-C, поэтому в последнее время это имеет тенденцию быть моим мышлением. Однако вопрос был о Swift.)

В Swift вместо этого вы используете необязательную цепочку и синтаксис:

object?.method().

С помощью этого синтаксиса Swift, если объект равен нулю, вызов метода пропускается.

ОЧЕНЬ ВАЖНО:

Если у вас есть 2 объекта, у каждого из которых есть слабые ссылки друг на друга, это хорошо, но где-то еще в вашей программе вам нужно иметь сильные (владеющие) ссылки на оба объекта или они будут освобождены.