Что такое retain cycle и как он возникает при взаимных сильных ссылках?

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

Ответ

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

Что происходит:

  1. Счетчик ссылок каждого объекта никогда не достигает нуля.
  2. Объекты остаются в памяти навсегда (до завершения приложения).
  3. Вызываются deinit этих объектов.

Пример создания цикла:

class Person {
    var bestFriend: Person? // Сильная ссылка по умолчанию
    init() { print("Person инициализирован") }
    deinit { print("Person деинициализирован") }
}

var alice: Person? = Person() // RefCount = 1
var bob: Person? = Person()   // RefCount = 1

alice?.bestFriend = bob  // RefCount bob = 2
bob?.bestFriend = alice  // RefCount alice = 2

alice = nil // RefCount alice = 1 (удерживается bob.bestFriend)
bob = nil   // RefCount bob = 1 (удерживается alice.bestFriend)
// Оба объекта НЕ освобождаются, deinit не печатается.

Способы разрыва цикла:

  • weak ссылка: Используется, когда ссылаемый объект может стать nil. ARC автоматически обнуляет такую ссылку.
    weak var bestFriend: Person?
  • unowned ссылка: Используется, когда ссылаемый объект имеет тот же или больший срок жизни и никогда не станет nil. Не обнуляется, но приводит к крашу при обращении к освобожденному объекту.
    unowned let bestFriend: Person
  • Использование value-типов (struct, enum): Они копируются и не создают циклических зависимостей.

Best practice: Всегда анализируйте отношения владения между объектами. Делегаты, замыкания, захватывающие self, и двунаправленные связи — типичные места для возникновения retain cycle.