Что такое reference cycle и как он возникает в iOS
Что проверяют
Понимаете ли вы, что подсчёт ссылок принципиально не умеет разрывать циклы, и знаете ли конкретные места в приложении, где цикл заводится сам собой. Вопрос базовый ровно до того момента, пока не спросят, где вы такой цикл находили и чем доказали, что починили.
Как отвечать
ARC освобождает объект, когда счётчик сильных ссылок падает до нуля. Цикл это замкнутая цепочка сильных ссылок: счётчики никогда не обнуляются, deinit не вызывается, память удерживается до конца работы приложения. Сборщика мусора нет, сам цикл не рассосётся.
Типовые места: замыкание, сохранённое в свойстве и захватившее self; делегат, объявленный сильной ссылкой; блочный наблюдатель уведомлений; таймер, держащий цель; подписка, сохранённая в объекте, который она же и захватывает; родитель и ребёнок, ссылающиеся друг на друга напрямую.
Разрывают цикл, делая одну ссылку в цепочке слабой. weak обязательно опциональна и сама обнуляется, unowned не опциональна и падает при обращении после освобождения, поэтому её берут только когда время жизни гарантировано.
protocol LoaderDelegate: AnyObject { func didLoad(_ items: [Item]) }
final class Loader {
weak var delegate: LoaderDelegate?
func load() {
service.fetch { [weak self] items in
guard let self else { return }
self.delegate?.didLoad(items)
}
}
}Где спотыкаются
Ставят слабый захват в каждое замыкание рефлекторно. Замыкание, которое не сохраняется, цикла не создаёт, а лишний слабый захват иногда приводит к обратному: объект успевает освободиться, проверка выходит из блока, и нужная работа молча не выполняется.
Берут unowned там, где объект вполне может умереть раньше, ради экономии на разворачивании опционала. Вместо утечки получают падение, которое воспроизводится только при быстром закрытии экрана и потому доживает до продакшена.
Считают проблему решённой, если приложение не падает. Цикл не даёт ни падения, ни предупреждения, он тихо копит память. Единственное доказательство это сработавший deinit и граф памяти, где на объект больше не ведёт удерживающая цепочка.