Вызывается ли deinit у экземпляра класса, если его инициализатор завершился ошибкой?
Нет. Если инициализатор класса завершился ошибкой, экземпляр считается неинициализированным, поэтому его deinit не вызывается. ARC освобождает уже созданные значения и сильные ссылки из полностью или частично инициализированных свойств.
ARC автоматизировал подсчёт ссылок, но не изменил правило, что deinit относится только к успешно созданному экземпляру. Это предотвращает обращение к объекту, который не прошёл инициализацию и не удовлетворяет инвариантам своего типа.
Инициализатор может создать вспомогательные объекты, записать их в свойства, а затем вернуть ошибку. Если ожидать вызов deinit исходного экземпляра, можно ошибочно разместить там освобождение ресурсов или обязательную финализацию.
Неверное предположение особенно опасно для сетевых дескрипторов, файлов и транзакций: частично инициализированный объект не обязан выполнять обычный протокол завершения жизни.
При ошибке из init Swift не передаёт частично созданный экземпляр в обычный жизненный цикл ARC. Поэтому его deinit не запускается.
Вместо этого Swift выполняет очистку уже инициализированных stored-свойств. Если такое свойство содержит сильную ссылку на объект, эта ссылка освобождается; когда счётчик сильных ссылок объекта достигает нуля, для него уже может быть вызван собственный deinit.
В этом примере напечатается сообщение для Resource, но не для Session. Ресурс освобождается при очистке инициализированного свойства, а Session не считается успешно созданным экземпляром.
Ресурсы, требующие симметричного отката, лучше захватывать во временные локальные значения и освобождать непосредственно на ветке ошибки либо передавать владение отдельному объекту, который уже полностью инициализирован. deinit подходит для очистки обычных stored-свойств успешного экземпляра, но не как универсальный обработчик неудачного init.
Класс открывает временный файл в начале init, затем проверяет конфигурацию и обнаруживает ошибку. Вариант с записью закрытия файла только в deinit ненадёжен: при ошибке инициализации deinit этого класса не будет вызван.
Можно закрывать файл вручную на каждой ветке ошибки, но при добавлении новых этапов легко пропустить очистку. Более устойчивый вариант — создать отдельный полностью инициализированный владеющий объект-обёртку и хранить ресурс в нём; при выходе из области ошибки ARC освободит обёртку, а её deinit закроет файл.
Такой подход разделяет ответственность: неудачный инициализатор явно откатывает собственные промежуточные действия, а успешно созданные вспомогательные объекты используют свой обычный жизненный цикл. В результате ресурс закрывается и при успехе, и при ошибке, без ожидания deinit частично созданного владельца.
Вопрос: Вызывается ли deinit у свойства-объекта, если инициализатор его владельца завершился ошибкой?
Ответ: Да, если после ошибки сильная ссылка на это свойство освобождается и других сильных владельцев нет. Отсутствие deinit у владельца не означает пропуск очистки его уже инициализированных свойств.
Вопрос: Может ли код после throw из инициализатора использовать частично созданный экземпляр?
Ответ: Нет. Экземпляр не возвращается вызывающему коду как значение и не становится доступным для обычного использования. Вызвавшая сторона получает ошибку, а Swift очищает инициализированные части объекта.
Вопрос: Можно ли рассчитывать на deinit для отката внешней операции, начатой внутри init?
Ответ: Нет, если операция относится к самому создаваемому типу и инициализация может завершиться ошибкой. Откат нужно выполнить явно на пути ошибки или поручить отдельному уже созданному объекту-владельцу. Это важно, потому что deinit вызывается только для экземпляра, который успешно прошёл инициализацию и затем стал недоступен для дальнейшего владения.