Как решить проблему цикла сильных ссылок (retain cycle) в Swift?

«Как решить проблему цикла сильных ссылок (retain cycle) в Swift?» — вопрос из категории Управление памятью, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Retain cycle (сильная ссылочная цикличность) возникает, когда два или более объекта удерживают друг друга сильными (strong) ссылками, что предотвращает их освобождение ARC (Automatic Reference Counting). Это приводит к утечкам памяти.

Основные способы решения:

  1. weak ссылка: Используется, когда один объект не должен "владеть" другим, и ссылка может стать nil (например, делегат).

    class Parent {
        var child: Child?
    }
    class Child {
        weak var parent: Parent? // weak разрывает цикл
    }
  2. unowned ссылка: Используется, когда время жизни ссылаемого объекта гарантированно совпадает или превышает время жизни ссылающегося объекта, и ссылка никогда не должна быть nil. Обращение к unowned ссылке после освобождения объекта вызывает краш.

    class Customer {
        let id: String
        var card: CreditCard?
        init(id: String) { self.id = id }
    }
    class CreditCard {
        let number: String
        unowned let customer: Customer // У карты всегда есть владелец
        init(number: String, customer: Customer) {
            self.number = number
            self.customer = customer
        }
    }
  3. Захват списков в замыканиях: Замыкания — это ссылочные типы. Если замыкание, принадлежащее объекту, захватывает self сильно, возникает цикл.

    class NetworkManager {
        var completion: (() -> Void)?
    
        func fetchData() {
            makeRequest { [weak self] result in // Захватываем self слабо
                self?.handleResult(result) // self становится опциональным
            }
        }
    }
Ключевые различия: Ссылка Опциональность Поведение при освобождении объекта
strong Да/Нет Удерживает объект в памяти.
weak Всегда опциональна (T?) Автоматически становится nil.
unowned Всегда неопциональна (T) Вызовет краш при обращении.

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