В какой ситуации объект Swift может быть уничтожен до конца лексической области видимости и как гарантировать его жизнь до завершения операции с небезопасным указателем?
Лексическая область видимости сама по себе не гарантирует время жизни объекта. ARC может освободить последнюю сильную ссылку, когда объект больше не нужен по семантике программы, даже если переменная формально ещё находится в области видимости. Для продления жизни объекта до конца конкретной операции используют withExtendedLifetime.
ARC появился как автоматизация управления подсчётом ссылок: разработчику больше не нужно вручную расставлять операции удержания и освобождения объектов. При этом ARC не является сборщиком мусора: он освобождает объект, когда исчезает последняя сильная ссылка, а компилятор может оптимизировать момент освобождения.
Проблема особенно заметна на границе Swift-кода и низкоуровневых API, где объект временно представлен небезопасным указателем. Такой указатель не является сильной ссылкой и сам по себе не удерживает объект в памяти.
Предположим, метод объекта передал его адрес внешней функции или сохранил небезопасный указатель для кратковременной операции. После последнего логического использования объект может быть освобождён ARC раньше, чем завершится операция, даже если исходная переменная ещё видна в исходном тексте.
Использование указателя после уничтожения объекта приводит к обращению к недействительной памяти. Последствия не обязаны проявляться сразу: возможны повреждение данных, аварийное завершение или внешне корректный, но неопределённый результат.
withExtendedLifetime явно сообщает Swift, что переданный объект должен оставаться живым на протяжении выполнения замыкания. Это не превращает указатель в безопасный и не продлевает жизнь объекта после выхода из замыкания.
В примере passUnretained не создаёт владение объектом, поэтому одной переменной address недостаточно. Вызов withExtendedLifetime удерживает resource живым до завершения тела замыкания.
Эту конструкцию применяют только для узкого участка, где действительно требуется гарантировать время жизни. Она не исправляет указатель, сохранённый после выхода из замыкания, не защищает от конкурентного доступа и не заменяет корректную модель владения внешним API.
Команда интегрировала Swift-обёртку над C-библиотекой. Обёртка передавала библиотеке адрес данных объекта, а затем выполняла короткий синхронный вызов. В оптимизированной сборке иногда возникало падение: к моменту чтения библиотека уже получала адрес уничтоженного объекта.
Рассматривались два варианта. Сохранить объект в дополнительном свойстве было бы избыточно и создало бы риск более долгого владения или цикла. Передать объект как владельца в API библиотеки было бы правильнее, если библиотека действительно хранит указатель после вызова, но для синхронной операции это меняло бы контракт без необходимости.
Выбрали withExtendedLifetime вокруг всего синхронного вызова. Это минимально продлило владение ровно на необходимый период; для асинхронного API применили бы отдельное явное владение до момента callback или завершения операции. После исправления обращение к недействительной памяти исчезло, а время жизни ресурса осталось ограниченным.
Нет. Компилятор ориентируется на последние семантически необходимые использования, а не только на границы блока исходного кода. Поэтому видимость имени переменной и фактическое время жизни объекта — разные понятия.
Нет. Небезопасный указатель содержит адрес, но не участвует в подсчёте сильных ссылок ARC. Пока указатель используется, объект должен удерживаться другим корректным способом; для краткой синхронной операции таким способом может быть withExtendedLifetime.
Нет. Гарантия действует только во время выполнения переданного замыкания. Если указатель должен использоваться позже, нужно определить владельца объекта на весь период такого использования и согласовать это с контрактом API; иначе после завершения замыкания указатель снова может стать недействительным.