При досрочном выходе из функции через throw когда ARC освобождает сильные локальные ссылки?
При распространении throw сильные локальные ссылки, которые больше не нужны после выхода из функции, освобождаются в процессе раскрутки стека. Если освобождение последней сильной ссылки приводит к нулевому счётчику, объект становится доступен для уничтожения, а его deinit может выполниться до передачи ошибки вызывающему коду.
Точный момент между отдельными инструкциями не следует выводить только из текстовой позиции переменной: ARC и компилятор управляют временем жизни с учётом семантических гарантий, а не простой лексической области видимости.
ARC появился как автоматизированная замена ручному управлению подсчёком ссылок в Objective-C. Разработчик сохраняет обычные сильные ссылки, а компилятор добавляет необходимые операции управления временем жизни.
Обработка ошибок особенно важна для памяти: функция может завершиться не через обычный return, а через throw. Без корректной очистки при раскрутке стека локальные объекты могли бы удерживаться дольше необходимого или утекать.
Функция может создать объект, передать его в несколько временных операций, а затем выбросить ошибку. После throw её обычное продолжение недоступно, поэтому локальные сильные ссылки должны быть корректно обработаны независимо от того, какой участок кода принимает ошибку.
Неверно считать, что throw автоматически уничтожает каждый объект, созданный функцией. Уничтожение зависит от наличия других сильных владельцев: свойства, коллекции, замыкания или вызывающий код могут продолжать удерживать тот же экземпляр.
Во время раскрутки стека Swift выполняет необходимые действия очистки для локальных значений. Для сильной ссылки это означает уменьшение счётчика владения, когда ссылка покидает свою семантическую область жизни или больше не требуется.
Если локальная ссылка была последним сильным владельцем, счётчик достигает нуля. Тогда запускается уничтожение объекта: сначала выполняется его deinit, после чего освобождается память экземпляра. Если существует другой сильный владелец, deinit не запускается.
throw не меняет правил ARC и не превращает ссылку в слабую. Он только выбирает путь выхода, на котором компилятор обязан выполнить соответствующую очистку. Объекты, удерживаемые weak-ссылками, при этом не продлевают время жизни; unowned-ссылки также не добавляют владения.
Важно различать освобождение ссылки и немедленное уменьшение памяти процесса. ARC может снять владение объектом, но распределитель памяти или Foundation могут оставить освобождённый блок доступным для повторного использования, поэтому RSS не обязан уменьшиться сразу.
Для внешних ресурсов полагаться только на момент deinit не следует. Если ресурс нужно закрыть строго до передачи ошибки, используйте явный метод освобождения или defer; это делает порядок завершения операции частью логики функции, а не побочным эффектом времени жизни объекта.
Сервис загружает временный объект и выбрасывает ошибку при проверке ответа. Рассматривались два варианта:
deinit: код короче, но момент освобождения зависит от всех владельцев и оптимизаций времени жизни;defer: ресурс закрывается при любом выходе из функции, включая throw, но требуется явно описать операцию завершения.Выбран второй вариант. defer гарантирует выполнение при обычном возврате и при выбрасывании ошибки, а ARC отдельно освобождает сам объект, когда исчезает последняя сильная ссылка. В результате сетевое соединение закрывается предсказуемо, даже если объект дополнительно удерживается журналированием или очередью повторной обработки.
1. Запускает ли throw deinit всех локальных объектов функции?
Нет. Он уменьшает владение локальными ссылками, которые очищаются при выходе, но deinit запускается только для объектов, у которых после этого не осталось сильных владельцев. Объект, сохранённый в свойстве или переданный в escaping-замыкание, продолжит жить.
2. Гарантирован ли порядок уничтожения нескольких локальных объектов при раскрутке стека?
Нельзя безоговорочно выводить такой порядок из порядка объявлений переменных. Компилятор может управлять временем жизни объектов по их последнему использованию, а разные зависимости владения меняют фактический результат. Если порядок критичен, его нужно выразить явно через defer или явное освобождение ресурса.
3. Достаточно ли deinit, чтобы освободить файл при возникновении ошибки?
Только если deinit действительно будет вызван в нужный момент, а это зависит от всех сильных ссылок и не является хорошим контрактом для внешнего ресурса. Для предсказуемого освобождения файла следует использовать явное закрытие в defer; deinit стоит рассматривать как резервную очистку, а не как основной протокол управления ресурсом.