Что такое сильная ссылка в Swift и какие проблемы могут возникнуть при множественных ссылках на объект?

Ответ

Сильная ссылка (strong reference) — это ссылка по умолчанию в Swift, которая увеличивает счетчик ссылок (retain count) объекта в куче. Пока существует хотя бы одна сильная ссылка, объект не будет освобожден.

Пример множественных сильных ссылок:

class Person {
    let name: String
    init(name: String) { self.name = name; print("(name) инициализирован") }
    deinit { print("(name) деинициализирован") }
}

var reference1: Person? = Person(name: "Alice") // Retain count = 1
var reference2: Person? = reference1             // Retain count = 2
var reference3: Person? = reference1             // Retain count = 3

reference1 = nil // Retain count = 2
reference2 = nil // Retain count = 1
reference3 = nil // Retain count = 0 -> Срабатывает deinit

Критическая проблема: Циклы сильных ссылок (Strong Reference Cycle) Возникает, когда два объекта ссылаются друг на друга через сильные ссылки, создавая изолированный граф, который никогда не будет освобожден.

class Person {
    let name: String
    var apartment: Apartment? // Сильная ссылка на Apartment
    init(name: String) { self.name = name }
    deinit { print("(name) освобожден") }
}

class Apartment {
    let unit: String
    var tenant: Person? // Сильная ссылка на Person
    init(unit: String) { self.unit = unit }
    deinit { print("Апартаменты (unit) освобождены") }
}

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

john!.apartment = unit4A
unit4A!.tenant = john // ЦИКЛ: Person → Apartment → Person

john = nil     // Объект Person НЕ освобождается (retain count = 1)
unit4A = nil   // Объект Apartment НЕ освобождается (retain count = 1)
// ПАМЯТЬ УТЕКАЕТ

Решение: Слабые (weak) и бесхозные (unowned) ссылки

  • weak — не увеличивает retain count. Становится nil, когда объект освобождается.

    class Apartment {
      weak var tenant: Person? // Решение: слабая ссылка
    }
  • unowned — как weak, но предполагает, что объект существует всегда. При обращении к освобожденному объекту — краш.

    class Customer {
      let card: CreditCard
      init(card: CreditCard) { self.card = card }
    }
    
    class CreditCard {
      unowned let customer: Customer // Customer всегда существует дольше
      init(customer: Customer) { self.customer = customer }
    }

Правило: Используйте weak для опциональных ссылок, которые могут стать nil. Используйте unowned для ссылок, которые всегда имеют значение в течение жизни объекта.

Ответ 18+ 🔞

А, ну вот, классика жанра, блядь! Сильные ссылки в Swift — это как тот самый Герасим, который за всё хватается и не отпускает, пока в рот ему чих-пых!

Смотри, есть у тебя объект в памяти, в этой куче, блядь. Сильная ссылка — это как будто ты взял его за жопу и кричишь: «Моё!». Пока хоть один такой придурок держит, объект живой. Счётчик этих держателей (retain count) увеличивается.

class Person {
    let name: String
    init(name: String) { self.name = name; print("(name) инициализирован") }
    deinit { print("(name) деинициализирован") }
}

var reference1: Person? = Person(name: "Alice") // Держу я! (count = 1)
var reference2: Person? = reference1             // И я держу! (count = 2)
var reference3: Person? = reference1             // О, и я с вами! (count = 3)

reference1 = nil // Я отпустил (count = 2)
reference2 = nil // И я отпустил (count = 1)
reference3 = nil // Все отпустили, ёпта! (count = 0) -> *бабах* deinit!

Всё вроде логично, да? Ан нет! Сейчас начнётся самое интересное, блядь — Циклы сильных ссылок. Это когда два упыря схватили друг друга в охапку и не хотят отпускать. Мёртвая хватка, ёпта!

class Person {
    let name: String
    var apartment: Apartment? // Сильно хватаю квартиру
    init(name: String) { self.name = name }
    deinit { print("(name) освобожден") }
}

class Apartment {
    let unit: String
    var tenant: Person? // Сильно хватаю жильца
    init(unit: String) { self.unit = unit }
    deinit { print("Апартаменты (unit) освобождены") }
}

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

john!.apartment = unit4A   // Джон хватает квартиру.
unit4A!.tenant = john      // Квартира хватает Джона. ПИЗДЕЦ, ЦИКЛ!

john = nil     // Ты думаешь, Джон освободился? Хуй там! Квартира же его держит!
unit4A = nil   // А квартира? А её Джон держит! Замкнутый круг, блядь!
// Оба объекта теперь болтаются в памяти, как призраки. УТЕЧКА! Совесть, как у Герасима, должна заглодать, но компилятор-то молчит!

Вот такая ебля получается. Два мудака друг за друга держатся, и оба сдохнуть не могут. Память течёт, как вода из дырявого ведра.

Спасение, блядь! Есть два выхода, как разжать эти ебучкие хватки.

  • weak — слабая ссылка. Это как сказать: «Я тебя держу, но если ты сдохнешь — я тебя отпущу и даже не заплачу». Ссылка становится nil, когда объект освобождается. Для опциональных типов — самое то.

    class Apartment {
        weak var tenant: Person? // Я не настаиваю, живи как знаешь.
    }
    // Теперь, когда Джона (john) прибьют (nil), квартира (unit4A) просто скажет: "Ну и хуй с ним, tenant = nil".
  • unowned — бесхозная ссылка. Это похуистичнее. Ты говоришь: «Этот объект будет жить ВСЕГДА, пока я жив. Я на него ссылаюсь, но не держу». А если он всё-таки сдохнет раньше тебя, и ты к нему обратишься — БАБАХ! Краш приложения, в рот меня чих-пых! Используй, только если на 100500% уверен в порядке жизни объектов.

    class Customer {
        let card: CreditCard
        init(card: CreditCard) { self.card = card }
    }
    
    class CreditCard {
        unowned let customer: Customer // Карта не может жить без владельца. Владелец всегда умрёт позже.
        init(customer: Customer) { self.customer = customer }
    }
    // Если Customer удалить, а CreditCard попытается достучаться до customer — будет тебе пиздец, runtime error.

Короче, правило простое, блядь:

  • weak — когда ссылка может стать nil (опциональная хуйня).
  • unowned — когда ссылка НИКОГДА не станет nil в течение всей жизни ссылающегося объекта (но это надо проверять, а то охуеешь потом).

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