При уничтожении экземпляра класса-наследника в каком порядке выполняются его deinit и deinit суперкласса?
Сначала выполняется deinit класса-наследника, затем автоматически вызывается deinit его суперкласса. Явно вызывать super.deinit нельзя: Swift делает это сам после завершения очистки наследника.
ARC автоматизирует управление временем жизни объектов через подсчёт сильных ссылок, но не отменяет необходимость выполнять пользовательскую очистку ресурсов. Для этого в Swift существует deinit — детерминированная точка, выполняемая перед освобождением экземпляра.
Такой порядок позволяет наследнику сначала закрыть или отсоединить ресурсы, зависящие от состояния базового класса, а затем передать очистку базовой части суперклассу.
Ошибка в предположении о порядке разрушения может привести к обращению к уже недоступному состоянию или к преждевременному освобождению ресурса. Особенно это важно, когда наследник владеет ресурсом, который использует инфраструктуру, принадлежащую суперклассу.
Нельзя полагаться на ручной вызов deinit или вызывать деструктор суперкласса из деструктора наследника. Нарушение этого правила невозможно исправить через дополнительное уменьшение числа ссылок: порядок завершения иерархии задаётся языком.
Когда количество сильных ссылок на экземпляр становится равным нулю, ARC делает экземпляр доступным для уничтожения. Если это экземпляр наследника, Swift выполняет его deinit, после чего автоматически выполняет deinit суперкласса и только затем завершает освобождение объекта.
Если у наследника нет собственного deinit, выполняется только цепочка доступных деструкторов, начиная с ближайшего суперкласса. Хранимые свойства освобождаются в рамках разрушения экземпляра, но код не должен зависеть от неявного порядка освобождения отдельных свойств; если порядок важен, его следует выразить явно в deinit.
deinit вызывается автоматически и обычно однократно для конкретного экземпляра. Однако сам момент, когда ARC обнаружит отсутствие сильных ссылок, нельзя использовать как замену синхронизации между потоками или как универсальный механизм управления внешними ресурсами.
Базовый класс управляет соединением с системой, а наследник регистрирует обработчик, использующий это соединение. Возможны два подхода: закрыть всё в одном deinit базового класса или разделить очистку между классами. Первый проще, но базовый класс начинает знать детали наследников; второй лучше соблюдает ответственность каждого класса, однако требует учитывать порядок — наследник должен сначала снять свой обработчик, затем суперкласс закроет соединение.
Выбранное решение — разместить очистку каждого принадлежащего ресурса в соответствующем deinit и не вызывать суперкласс вручную. В результате обработчик удаляется до закрытия базового соединения, а цепочка ARC остаётся предсказуемой и не зависит от внешнего кода.
1. Нужно ли явно вызывать super.deinit?
Нет. Swift автоматически вызывает деструктор суперкласса после завершения deinit наследника. Попытка представить этот вызов как обычный метод ошибочна: deinit нельзя вызвать вручную.
2. Что произойдёт, если у наследника нет собственного deinit?
Очистка всё равно продолжится по иерархии: будет вызван deinit ближайшего суперкласса, затем деструкторы его предков. Наличие собственного деструктора нужно только тогда, когда наследнику требуется освободить принадлежащие ему ресурсы.
3. Можно ли использовать deinit для гарантированного порядка освобождения свойств?
Надёжно полагаться на неявный порядок уничтожения отдельных свойств не следует. Если один ресурс должен быть освобождён до другого, это нужно явно выразить в deinit, например сначала отписаться от источника событий, а затем освободить объект, который использовался обработчиком.