Сравните динамический тип объекта и тип переменной: что напечатает код и почему type(of:) даёт такой результат?
class Animal {}
final class Cat: Animal {}
let animal: Animal = Cat()
print(type(of: animal) == Cat.self)
print(type(of: animal) == Animal.self)
Код напечатает true, затем false. type(of:) возвращает метатип фактического объекта во время выполнения, поэтому для значения animal это Cat, несмотря на статический тип переменной Animal.
Статическая типизация Swift позволяет проверять корректность операций на этапе компиляции, но при работе с базовыми классами и протоколами конкретный тип объекта может быть известен только во время выполнения. Для таких случаев нужны механизмы динамического типа, приведения и сопоставления.
type(of:) решает задачу точного определения фактического типа экземпляра. Это отличается от проверки совместимости через is: совместимость отвечает на вопрос, можно ли рассматривать объект как некоторый тип, а type(of:) позволяет сравнить его с конкретным динамическим типом.
Статический тип animal — Animal, но созданный экземпляр имеет тип Cat. Если перепутать эти понятия, можно ошибочно ожидать, что проверка на Animal подтверждает именно тип базового класса.
Такое различие важно при выборе обработчика для точного вида объекта. Использование type(of:) вместо is или условительного приведения меняет смысл проверки и может привести к тому, что подклассы будут обработаны не так, как предполагалось.
type(of: animal) анализирует динамический тип значения и возвращает метатип Cat.Type. Выражение Cat.self также обозначает метатип Cat.Type, поэтому первое сравнение истинно.
Animal.self имеет метатип Animal.Type. Хотя объект Cat совместим с Animal, его фактический тип не становится Animal; поэтому второе сравнение ложно.
Проверка animal is Animal означает «объект можно использовать как Animal». Она успешна и для экземпляров Animal, и для экземпляров его подклассов. Сравнение type(of: animal) == Animal.self требует точного совпадения динамического типа и не считает Cat равным Animal.
Если нужна работа с возможностями конкретного подкласса, обычно применяют условительное приведение as?, а не сравнение метатипов. Сравнение через type(of:) оправдано, когда обработка должна зависеть именно от точного типа, включая отказ от обработки его подклассов.
Предположим, приложение получает объекты базового типа Animal. Для большинства животных действует общий обработчик, но для точного типа Cat требуется специальная телеметрия.
Вариант с is прост и позволяет обращаться к API Cat, но он также сработает для будущего подкласса SiameseCat. Это плюс при полиморфной обработке, но минус, если нужна логика только для обычного Cat.
Вариант с условительным приведением as? Cat проверяет совместимость и извлекает объект. Он обычно предпочтительнее, когда нужно использовать свойства или методы Cat; однако для подклассов он тоже будет успешным.
Выбранное решение — сравнение type(of:) с Cat.self, если требование действительно состоит в точном совпадении типа. В результате Cat получает специальную обработку, а SiameseCat не попадает в неё автоматически. Если позднее появятся новые подклассы, это поведение нужно явно пересмотреть.
Чем type(of:) == Cat.self отличается от animal is Cat?
animal is Cat проверяет совместимость с Cat, поэтому возвращает true и для экземпляра любого подкласса Cat. Сравнение метатипов проверяет точное равенство динамического типа и для подкласса вернёт false.
Меняется ли динамический тип после восходящего приведения к базовому классу?
Нет. При присваивании Cat переменной типа Animal меняется доступный статический интерфейс переменной, но сам экземпляр остаётся объектом Cat. Поэтому type(of:) продолжает возвращать Cat.Type.
Когда сравнение через type(of:) хуже условительного приведения?
Когда нужно не только определить тип, но и безопасно получить значение с интерфейсом конкретного типа. as? Cat сразу даёт Cat?, позволяет использовать API Cat и корректно обрабатывает несовместимое значение без аварийного завершения. Сравнение метатипов само по себе объект не приводит и не предоставляет доступ к дополнительным членам подкласса.