Что такое retain cycle в управлении памятью?

Ответ

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

Почему это проблема: ARC освобождает объект только когда счётчик сильных ссылок на него становится равным нулю. В цикле счётчик никогда не достигнет нуля.

Пример возникновения:

class Person {
    var dog: Dog?
    deinit { print("Person освобождён") }
}

class Dog {
    var owner: Person? // Strong reference → создаёт цикл
    deinit { print("Dog освобождён") }
}

var john: Person? = Person()
var fido: Dog? = Dog()
john?.dog = fido
fido?.owner = john // Цикл создан!
john = nil
fido = nil // Ни один deinit не вызовется, память не освободится

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

  1. weak ссылка: Используется, когда ссылаемый объект может стать nil.
    class Dog {
        weak var owner: Person?
    }
  2. unowned ссылка: Используется, когда ссылаемый объект гарантированно существует в течение всей жизни ссылающегося объекта (риск краша, если объект освобождён).
    class CreditCard {
        unowned let customer: Customer // Карта не может существовать без клиента
    }

Частый случай в замыканиях: Замыкания — reference types. Захват self без указания weak создаёт retain cycle.

lazy var someClosure: () -> Void = { [weak self] in // Важно: [weak self]
    self?.doSomething()
}

Ответ 18+ 🔞

О, слушай, вот это история, блядь! Прямо как в жизни, только в коде. Есть такая штука — retain cycle, или, по-нашему, цикл сильных ссылок. Это когда два объекта держатся друг за дружку так, что отпустить не могут, как два пьяных мужика у подъезда — один другого поддерживает, а оба на ногах не стоят, и оба домой не попадут, блядь.

В чём, сука, проблема-то? ARC, наш добрый мусорщик, освобождает память, только когда на объект никто не ссылается. А если они друг на друга ссылаются крепко-накрепко, то счётчик ссылок у них никогда в ноль не упадёт. И сидят они в памяти, как призраки, нахер никому не нужные, но место жрут. Утечка памяти, пиздец.

Как это, блядь, выглядит? Смотри, классика жанра:

class Person {
    var dog: Dog?
    deinit { print("Person освобождён") }
}

class Dog {
    var owner: Person? // Вот она, сильная ссылка! Цикл родился, ёпта!
    deinit { print("Dog освобождён") }
}

var john: Person? = Person()
var fido: Dog? = Dog()
john?.dog = fido
fido?.owner = john // Всё, пиши пропало! Связали их навеки!
john = nil
fido = nil // А деиниты не вызовутся! Объекты навечно в памяти, как в тюрьме!

И что делать, ёбушки-воробушки? Ломать этот порочный круг!

  1. weak ссылка (слабая): Это как сказать: «Я на тебя ссылаюсь, но если тебя не станет — и хуй с тобой, я в nil превращусь». Используй, когда объект может спокойно исчезнуть.
    class Dog {
        weak var owner: Person? // Теперь не держит хозяина в ежовых рукавицах
    }
  2. unowned ссылка (бесхозная): Это более жёсткая история. Типа: «Ты точно будешь жить, пока я жив, иначе я крашнусь». Рискованный пиздец, но если уверен на 100500% — валяй.
    class CreditCard {
        unowned let customer: Customer // Карта без владельца? Да хуйня какая-то, не бывает!
    }

Отдельная песня — замыкания! Вот где народ чаще всего обжигается, блядь. Замыкание — оно тоже объект, и если оно внутри себя захватывает self без спросу, то получается тот же цикл: объект держит замыкание, замыкание держит объект. Идиотизм!

lazy var someClosure: () -> Void = { [weak self] in // Смотри, блядь, магические слова! [weak self]!
    self?.doSomething() // И обращаемся осторожненько, через вопросик
}

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