Как работает механизм управления памятью ARC в Swift
Что проверяют
Понимаете ли вы, что ARC работает на этапе компиляции и не собирает циклы, и умеете ли находить и разрывать циклы сильных ссылок. Следом почти всегда просят показать утечку через замыкание и объяснить разницу между weak и unowned.
Как отвечать
ARC считает ссылки на экземпляры классов, акторов и захваченный контекст замыканий. Структуры и перечисления это значимые типы, у них счётчика нет, хотя поля-ссылки внутри них считаются. Вызовы retain и release вставляет компилятор, отдельного сборщика во время работы нет. Когда сильных ссылок не остаётся, сразу вызывается deinit: освобождение детерминированное, в отличие от GC, но инкременты атомарные и в горячем коде не бесплатны.
Три вида ссылок:
- strong: по умолчанию, держит объект в памяти;
weak: не увеличивает сильный счётчик, всегда опциональныйvarи сам обнуляется, когда объект уходит;unowned: тоже не держит, но не обнуляется. Обращение к освобождённому объекту даёт аварийное завершение. Подходит, когда ссылающийся гарантированно живёт не дольше объекта.
Главная проблема ARC это цикл: A держит B, B держит A, счётчики не падают до нуля. Типичные места:
- делегаты:
weak var delegate: SomeDelegate?, протокол ограниченAnyObject; - замыкания: сохранённое замыкание захватывает
self, решение через[weak self]иguard let self else { return }; Timerи подписки Combine: таймер держит цель, аsink, сохранённый вcancellablesтого же объекта, держитself.
Находить утечки удобно в Memory Graph Debugger в Xcode и в инструменте Leaks в Instruments.
Где спотыкаются
Называют ARC сборщиком мусора. Сборщик ищет недостижимые объекты, а ARC просто считает ссылки и цикл из двух объектов не освободит никогда.
Ставят [weak self] везде, включая map и UIView.animate, где замыкание не хранится у самого объекта и цикла нет, или ставят unowned там, где объект может исчезнуть, и получают падение.
Уверены, что объект живёт до конца области видимости. Swift вправе освободить его сразу после последнего использования, поэтому для таких случаев есть withExtendedLifetime.