В async-функции локальная сильная ссылка на объект не используется после await. Обязан ли ARC удерживать объект во время приостановки?
Нет. ARC не обязан удерживать объект до конца области видимости локальной переменной: если после await объект больше не нужен, компилятор может освободить его до приостановки или во время неё. Если объект используется после await, его сильное владение должно сохраняться до этого использования.
await сам по себе не является гарантией времени жизни объекта. Для обязательного удержания во время асинхронной операции нужна явная модель владения: например, сильная ссылка в состоянии операции или реальное использование объекта после возобновления.
ARC появился как автоматизированная альтернатива ручному управлению подсчётом ссылок. Он снимает с разработчика необходимость явно добавлять и убирать владение, но сохраняет фундаментальное правило: объект жив, пока существует хотя бы одно сильное владение.
Чтобы оптимизировать код, компилятор может размещать операции удержания и освобождения не строго на границах исходной области видимости, а в точках, где это безопасно с точки зрения семантики программы. Поэтому область видимости переменной и гарантированное время жизни объекта — не одно и то же.
Асинхронная функция может приостановиться на await, а затем продолжить выполнение позже, возможно на другом потоке. Разработчик иногда ошибочно считает, что локальная переменная автоматически удерживает объект на всём промежутке между этими событиями.
Это опасно, если объект должен оставаться живым во время ожидания: например, он регистрирует внешний ресурс, поддерживает состояние операции или в deinit выполняет обязательное действие. Если после await нет семантически необходимого обращения к объекту, ARC может завершить его жизнь раньше ожидаемого.
Минимальная иллюстрация различия:
Здесь вызов check() после await делает объект необходимым до этой точки. Само наличие локальной переменной без последующего использования такой гарантии не даёт.
При компиляции async-функции её состояние между приостановками представляется специальным контекстом возобновления. В этот контекст попадают значения, которые действительно нужны после await; соответствующие сильные владения сохраняются. Локальная ссылка, которая больше не влияет на результат, может быть освобождена раньше.
Таким образом, важен не факт существования переменной, а её семантическое последнее использование. Оптимизатор вправе сократить время жизни объекта, если это не меняет наблюдаемое корректное поведение программы. Вызов deinit обычно не следует использовать как способ синхронизации или как гарантию момента освобождения.
Если объект должен пережить await, возможны такие варианты:
await, если это соответствует смыслу операции;withExtendedLifetime, но помнить, что он не превращает произвольный асинхронный интервал в одну синхронную область жизни.withExtendedLifetime гарантирует жизнь значения только на время выполнения переданного синхронного замыкания. Он не предназначен для удержания объекта через отдельный await; для этого владение должно находиться в состоянии, которое сохраняется между приостановками.
Важно отличать время жизни объекта от времени жизни самой async-задачи. Задача может продолжать выполняться, но не обязана удерживать объект, который больше не нужен её продолжению. И наоборот, захват объекта замыканием задачи создаёт сильное владение, если захват не объявлен как weak или unowned.
Сервис запускает асинхронную операцию, а его экземпляр должен оставаться живым до завершения сетевого обмена, потому что в deinit он освобождает связанный ресурс. Разработчик создаёт локальный экземпляр перед await, но после ожидания не обращается к нему и ожидает, что локальная переменная удержит его.
Рассматривались варианты:
await. Это может удержать объект, но делает время жизни зависимым от неочевидного технического приёма.Выбран третий вариант: состояние операции хранит сильную ссылку и очищает её после успешного завершения, ошибки или отмены. В результате время жизни объекта связано с жизненным циклом операции, а не с оптимизациями ARC и случайным последним использованием локальной переменной.
Вопрос: гарантирует ли await сохранение всех локальных сильных ссылок до возобновления функции?
Нет. В контексте приостановки сохраняются только значения, необходимые для продолжения корректного выполнения. Неиспользуемая после await ссылка может быть освобождена раньше. Поэтому await — точка потенциальной приостановки, но не универсальная граница времени жизни всех локальных объектов.
Вопрос: достаточно ли добавить чтение объекта после await, чтобы он был жив во время самого ожидания?
Обычно да, если это чтение является реальным семантически значимым использованием, которое нельзя безопасно удалить. Однако искусственный холостой доступ — плохой дизайн: он скрывает намерение. Для ресурса, который должен жить именно во время ожидания, лучше выразить владение через состояние операции или другой явный объект-владелец.
Вопрос: может ли deinit служить сигналом о завершении асинхронной операции?
Нет, надёжно рассчитывать на это нельзя. ARC определяет момент освобождения по наличию сильных владений и оптимизациям, а задача, замыкание, кэш или другой владелец могут продлить жизнь объекта. Завершение операции следует оформлять явным методом, состоянием или структурой управления ресурсом, а deinit оставлять для финальной очистки, не требующей точного времени запуска.