Два независимых объекта созданы в одной функции, а их deinit записывает сообщения в журнал. Какой порядок их уничтожения можно считать гарантированным?
Никакой конкретный порядок уничтожения двух независимых объектов гарантировать нельзя. ARC может освободить каждый объект после его последнего использования, поэтому порядок не обязан совпадать ни с порядком создания, ни с обратным порядком объявлений.
Гарантии появляются только из графа владения: если один объект сильно владеет другим, их время жизни связано этой ссылкой. Для независимых объектов порядок следует задавать явно через отдельный протокол завершения работы, а не через побочные эффекты в deinit.
ARC появился как способ автоматизировать ручное управление подсчётом ссылок, сохранив предсказуемую модель освобождения объектов без сборщика мусора. Компилятор сам вставляет операции удержания и освобождения сильных ссылок.
Такая модель не означает, что уничтожение привязано к концу лексической области видимости. Компилятор и оптимизатор могут учитывать фактическое последнее использование объекта, поэтому полагаться на текстовый порядок исходного кода нельзя.
Предположение о порядке deinit часто появляется из наблюдения за одной сборкой: объекты могут уничтожаться в обратном порядке создания, и это выглядит закономерностью. Однако изменение оптимизации, способа использования переменных или версии компилятора может изменить момент освобождения.
Если логика программы зависит от того, какой deinit сработал первым, возможны гонки завершения, обращение к уже закрытому ресурсу или некорректное освобождение зависимых компонентов. deinit следует использовать для финальной очистки, но не как механизм координации независимых объектов.
Для локальной сильной ссылки ARC отслеживает необходимость сохранять объект до его последнего требуемого использования. После этого ссылка может быть освобождена, даже если переменная формально остаётся в области видимости.
У двух независимых объектов нет отношения владения, которое задавало бы их взаимный порядок. Поэтому компилятор может освободить первый объект раньше, второй раньше или практически одновременно с точки зрения наблюдаемого результата.
Даже если конкретный запуск напечатает second, затем first, это не является контрактом Swift. Нельзя строить корректность программы на таком порядке.
Если объект должен жить до определённого места, применяют withExtendedLifetime, но этот механизм только продлевает жизнь указанного объекта и не задаёт порядок уничтожения нескольких объектов. Если порядок действительно важен, используют явное действие вроде close() или shutdown(), после чего последовательно обнуляют переменные, содержащие сильные ссылки.
При этом явное обнуление управляемых optional-ссылок задаёт порядок освобождения именно этих ссылок, но не отменяет другие сильные ссылки на те же объекты. Поэтому сначала нужно проверить весь граф владения.
Компонент Cache и компонент Logger создаются при запуске экрана. Разработчик рассчитывает, что сначала уничтожится Cache, а затем Logger, поскольку Logger записывает в журнал сообщение об освобождении кэша.
Вариант с зависимостью через сильную ссылку формально связывает время жизни объектов, но создаёт ненужный граф владения и может привести к циклу. Вариант с ожиданием порядка deinit не имеет гарантии и ломается при оптимизации.
Надёжное решение — предоставить компонентам явный метод завершения работы, вызвать его в требуемой последовательности, а deinit оставить для страховочной очистки ресурсов. После этого каждая переменная или владеющая структура освобождается независимо, а порядок бизнес-операций не зависит от ARC.
1. Гарантирует ли обратный порядок объявлений локальных переменных порядок deinit?
Нет. Такой порядок может наблюдаться в конкретной конфигурации, но не гарантирован языком. ARC ориентируется на время последнего использования и граф сильных ссылок, а не на механическое удаление локальных переменных в обратном порядке текста.
2. Решает ли withExtendedLifetime проблему порядка уничтожения двух объектов?
Нет. Он гарантирует, что переданный объект не будет уничтожен раньше конца переданного замыкания. Он не заставляет другой объект уничтожиться до или после него и не устраняет дополнительные сильные ссылки.
3. Можно ли сделать порядок надёжным, вызвав deinit вручную?
Нет. deinit вызывается системой управления временем жизни, когда экземпляр становится недоступен через сильные ссылки, и напрямую вызвать его нельзя. Для управляемого порядка нужен обычный явно вызываемый метод завершения работы, например shutdown(), а deinit должен выполнять только финальную очистку, не являющуюся частью протокола взаимодействия объектов.