В объекте хранится escaping-замыкание, которое захватывает этот же объект: почему объект может не освободиться?
Объект может не освободиться из-за цикла сильных ссылок: объект сильно удерживает замыкание через своё свойство, а замыкание при захвате self сильно удерживает объект. ARC не освобождает такие объекты, потому что их счётчик ссылок не достигает нуля.
Обычно цикл разрывают захватом self через weak или unowned, либо явным обнулением свойства с замыканием. Выбор зависит от того, может ли объект существовать дольше замыкания.
В Swift управление памятью основано на Automatic Reference Counting. Замыкания являются значениями, которые могут сохраняться после завершения функции, поэтому они должны сохранять захваченные значения на всё время собственного существования.
Это решает проблему использования локальных данных в отложенных callbacks, но создаёт риск цикла: сохраняющий объект и сохранённое замыкание начинают удерживать друг друга.
Типичный сценарий — объект контроллера хранит callback для обновления состояния, а callback обращается к self. Если свойство объекта сохраняет замыкание, а замыкание по умолчанию захватывает self сильно, образуется цикл.
Последствие — не вызывается deinit, освобождение ресурсов откладывается или не происходит вовсе. Особенно опасны такие циклы для таймеров, подписок, сетевых задач и долгоживущих менеджеров событий.
При обычном захвате self замыкание сохраняет сильную ссылку на объект. Если объект хранит это замыкание, получается цепочка объект → замыкание → объект.
weak не увеличивает счётчик ссылок и автоматически становится nil после освобождения объекта. Поэтому замыкание может продолжить существовать, но перестанет выполнять действие для уже уничтоженного объекта.
unowned также не удерживает объект, но не становится nil. Если замыкание будет вызвано после уничтожения объекта, произойдёт ошибка времени выполнения. Его следует использовать только при доказанной гарантии, что замыкание не переживёт объект.
Иногда лучше не захватывать self вообще: можно сохранить в замыкании независимое значение или передать необходимые данные параметром. Другой вариант — явно обнулить callback в момент завершения подписки, но это требует корректного управления жизненным циклом.
Экран хранит обработчик события, а менеджер событий сохраняет экран или его замыкание. Вариант с сильным захватом прост, но может удерживать экран после закрытия и вызывать утечки памяти.
Захват через unowned не создаёт утечку, однако опасен при асинхронном callback: событие может прийти после уничтожения экрана. Явное обнуление свойства эффективно, но легко забыть выполнить его во всех путях завершения.
Для callback, который может сработать после закрытия экрана, обычно выбирают [weak self]. Это предотвращает цикл и безопасно игнорирует событие, если экран уже уничтожен. Если callback обязан завершиться раньше объекта по строгому контракту жизненного цикла, допустим unowned.
self создаёт утечку?Нет. Сильный захват сам по себе не является утечкой. Проблема возникает, когда одновременно существует обратная сильная ссылка от self к замыканию или через другую долгоживущую структуру. Если замыкание не хранится объектом либо хранится недолго и затем освобождается, цикл может не образоваться.
[weak self] от проверки существования объекта перед созданием замыкания?Проверка объекта до создания замыкания не предотвращает сильный захват внутри самого замыкания. Замыкание всё равно может сохранить self и сформировать цикл. Нужно изменить способ захвата, например использовать [weak self], либо захватывать только независимые значения.
Во время вызова замыкание может временно удерживаться вызывающим кодом. Кроме того, изменение свойства, из которого замыкание извлекается, может быть связано с конкурирующим доступом или сложным жизненным циклом callback. Надёжнее определить явную процедуру отмены подписки и атомарно прекратить удержание обработчика в владельце ресурса.