Когда происходит деалокация объекта при достижении счетчика ссылок нуля в Swift
Что проверяют
Понимаете ли вы, что ARC освобождает объект синхронно и детерминированно, в отличие от сборщика мусора, и где эта детерминированность кончается. Следом спрашивают, на каком потоке выполнится deinit и почему объект может не освободиться вовсе.
Как отвечать
Когда исчезает последняя сильная ссылка, deinit вызывается сразу, в том же месте и на том же потоке, где произошёл последний release. Отложенного цикла сборки нет. Детали:
- порядок: сначала
deinitподкласса, затем суперкласса. Хранимые свойства освобождаются уже после телаdeinit, поэтому внутри него они ещё доступны; - память и
unowned: послеdeinitпамять объекта освобождается, только если на него нетunowned-ссылок. Обращение поunownedк деинициализированному объекту приводит к крашу, аweakвернётnil; - конец области видимости не гарантирован: оптимизатор может отпустить объект сразу после последнего использования, раньше закрывающей скобки. Если время жизни важно, используйте
withExtendedLifetime; - autorelease: объекты, пришедшие из Objective-C API как autoreleased, освобождаются при сливе пула, обычно в конце итерации run loop. В долгих циклах помогает
autoreleasepool {}; - поток: по умолчанию
deinitне изолирован даже у@MainActor-класса. Если последнюю ссылку на контроллер отпустило замыкание на фоновой очереди,deinitвыполнится на фоне; - у кого есть
deinit: у классов и акторов, а с Swift 5.9 и у некопируемых структур и перечислений (~Copyable).
Отдельно назовите случай, когда ноль не наступит: цикл сильных ссылок. Типичные источники: замыкание, захватившее self, сильный delegate, таймер, держащий цель. Лечится [weak self] и weak var delegate, а ищется через Memory Graph Debugger в Xcode и инструмент Leaks.
Где спотыкаются
Говорят, что объект «удалится когда-нибудь потом», путая ARC с GC, или, наоборот, обещают освобождение строго на закрывающей скобке.
Кладут в deinit работу с UIKit или состоянием главного актора, не думая о потоке, на котором отпущена последняя ссылка.
Лечат все замыкания через unowned self. Если замыкание переживёт объект, это краш вместо безопасного nil.