Почему deinit не подходит как единственный механизм своевременного освобождения внешнего ресурса?
deinit не гарантирует заранее известный момент выполнения, поэтому его нельзя использовать как единственный механизм своевременного освобождения файла, сокета, блокировки или другого внешнего ресурса. ARC запускает deinit, когда объект становится недостижимым по сильным ссылкам, но это может произойти позже ожидаемого, не произойти до завершения процесса или не произойти из-за retain cycle.
Для внешних ресурсов нужен явный жизненный цикл: отдельный метод закрытия, defer, контекстный менеджер или другой API, который гарантирует освобождение в нужной точке. deinit следует оставлять защитным резервным механизмом и местом для освобождения внутренних ресурсов объекта.
ARC автоматизирует управление памятью объектов классов через подсчёт сильных ссылок. Его задача — освободить память, когда объект больше не может быть достигнут через сильные ссылки, не заставляя разработчика вручную вызывать retain и release.
Внешние ресурсы подчиняются другой модели. Файл, сокет, системная блокировка или подписка могут требовать освобождения в конкретный момент, независимо от того, когда оптимизатор и ARC сочтут объект недостижимым. Поэтому автоматическое управление памятью не заменяет детерминированное управление ресурсами.
Представим объект, который открывает файл в инициализаторе, а закрывает его только в deinit. Если где-то сохраняется сильная ссылка, файл останется открытым дольше, чем нужно. Если образуется цикл сильных ссылок, объект вообще не будет уничтожен обычным ARC, пока цикл не будет разорван.
Есть и обратная проблема: полагаться на deinit при завершении процесса нельзя. Приложение может завершиться аварийно, процесс может быть принудительно остановлен, а выполнение завершающего кода для ещё живых объектов не является гарантией, на которую следует опираться при сохранении данных или закрытии критичных ресурсов.
Момент вызова deinit определяется переходом количества сильных ссылок к нулю. На него влияют владение объектом другими экземплярами, замыканиями, задачами, контейнерами и глобальными или статическими свойствами. Лексический конец области видимости не является универсальной гарантией точного момента уничтожения: компилятор может учитывать последнее использование, а дополнительные владельцы могут продлить жизнь объекта.
Retain cycle делает объект недостижимым извне, но оставляет ненулевой счётчик сильных ссылок внутри цикла. ARC не может самостоятельно освободить такой граф, поэтому ресурс, закрываемый только в deinit, может остаться занятым на неопределённый срок.
Надёжный подход разделяет две ответственности:
deinit выполняет защитную очистку, если разработчик или вызывающий код нарушил контракт.Явная очистка должна быть безопасной при повторном вызове: после первого закрытия состояние объекта переводится в состояние «ресурс уже освобождён». Это важно, потому что метод очистки может быть вызван вручную, из defer и дополнительно из deinit.
Если ресурс берётся внутри одной синхронной операции, подходящим инструментом обычно является defer: он связывает освобождение с выходом из функции, включая выход через ошибку или ранний return. Если ресурс принадлежит объекту с более долгим жизненным циклом, нужен явный метод вроде close, cancel или invalidate, вызываемый владельцем в момент завершения использования.
weak и unowned помогают управлять владением объектами, но не создают гарантию своевременного закрытия внешнего ресурса. Например, слабая ссылка может предотвратить цикл, однако объект всё равно будет жить, пока существует другая сильная ссылка, а его deinit наступит позже.
Сервис загрузки открывает сетевую сессию и хранит её в объекте операции. Контроллер запускает операцию, затем пользователь закрывает экран. Если контроллер и операция удерживают друг друга сильными ссылками, возникает цикл. В результате экран исчезает, но сессия и связанные ресурсы продолжают жить.
Рассматривались два варианта. Первый — закрывать сессию только в deinit: решение короткое, но оно не устраняет цикл и не даёт гарантированного момента остановки. Второй — добавить явный cancel, разорвать взаимное владение там, где это необходимо, и вызвать cancel при закрытии экрана; этот вариант требует дисциплины, зато делает завершение операции предсказуемым.
Выбран второй вариант. Метод cancel прекращает работу, освобождает сессию и безопасен при повторном вызове, а deinit дополнительно проверяет, что ресурс не остался открытым. В результате ресурс освобождается при отмене операции, а не в неопределённый момент уничтожения объекта.
1. Может ли явный метод очистки полностью заменить deinit?
Он должен быть основным механизмом для внешних ресурсов, но не всегда полностью заменяет deinit. Ошибка вызывающего кода может привести к тому, что метод не вызван, поэтому deinit полезен как последняя защитная проверка. Однако полагаться только на него нельзя, а повторная очистка должна быть безопасной.
2. Разрывает ли weak-ссылка проблему с ресурсом автоматически?
Нет. weak лишь не увеличивает число сильных владельцев и автоматически обнуляется после уничтожения объекта. Пока существует хотя бы одна другая сильная ссылка, объект и его ресурс продолжают жить; кроме того, слабая ссылка не гарантирует, что объект будет уничтожен именно в момент, нужный бизнес-логике.
3. Почему отсутствие retain cycle всё равно не делает deinit детерминированным?
Даже без цикла объект может удерживаться контейнером, замыканием, задачей, статическим свойством или другой временной сильной ссылкой. Кроме того, выполнение deinit не следует рассматривать как гарантию при завершении процесса. Поэтому отсутствие цикла означает лишь возможность обычного освобождения через ARC, но не фиксированный момент освобождения внешнего ресурса.