Зачем в бизнес-логике используются слабые ссылки в Swift?
Что проверяют
Понимаете ли вы, как ARC считает ссылки и откуда в слое логики берутся циклы удержания: делегаты, замыкания, подписки. Следом обычно спрашивают, чем weak отличается от unowned и как вы находите утечку.
Как отвечать
ARC освобождает объект, когда счётчик сильных ссылок падает до нуля. Если сервис держит презентер, а презентер держит сервис, счётчики не обнулятся, и оба объекта останутся в памяти после закрытия экрана.
weak не увеличивает счётчик и сам обнуляется, когда объект уничтожен, поэтому это всегда опциональная var. Где это нужно в бизнес-логике:
- делегаты и output: интерактор сообщает результат презентеру через
weak var output: InteractorOutput?. Протокол должен быть ограничен классами (protocol InteractorOutput: AnyObject), иначеweakне скомпилируется; - сохраняемые замыкания: колбэк сервиса в свойстве или подписка Combine, где
sinkзахватываетself, аAnyCancellableлежит вcancellablesтого же объекта. Здесь пишут[weak self]иguard let self else { return }; - наблюдатели и реестры: список подписчиков, который не должен продлевать им жизнь, например
NSHashTable.weakObjects().
unowned тоже не держит объект, но не опционален, и обращение к уже уничтоженному объекту роняет приложение. Его ставят, только когда владелец гарантированно живёт дольше.
Утечки ищут через Memory Graph Debugger в Xcode, инструмент Leaks в Instruments и простой print в deinit.
Где спотыкаются
Ставят [weak self] в каждое замыкание. DispatchQueue.main.async или UIView.animate цикла не создают, а в запросе, результат которого нужно сохранить, слабая ссылка молча теряет ответ, если объект успел освободиться.
Делают модель зависимой от конкретного ViewController через слабую ссылку. Цикла нет, но слой логики знает про UI, правильнее протокол.
Считают, что Task { } всегда даёт утечку. Он удерживает self до завершения, это продление жизни, а цикл появляется, только если задача хранится в свойстве самого объекта и не заканчивается.