Допустимо ли присвоить Optional подкласса переменной Optional базового класса, и каким механизмом Swift это обеспечивает?
Да. Optional<Подкласс> можно передать туда, где ожидается Optional<БазовыйКласс>, потому что Swift применяет ковариантное преобразование: сначала рассматривает отношение подтипов между обёрнутыми типами, затем сохраняет структуру Optional. nil остаётся nil, а объект не копируется и не изменяет свой динамический тип.
Optional появился как типобезопасная замена неявным null-значениям и специальным значениям вроде отрицательного идентификатора. Чтобы его использование не требовало постоянного ручного преобразования при работе с иерархиями классов, Swift разрешает безопасно поднимать тип содержимого Optional по отношению подтипов.
Это не означает, что все обобщённые типы Swift ковариантны. Такое преобразование является поддерживаемым языком преобразованием для Optional и не превращает пользовательский контейнер одного параметра типа в контейнер другого параметра типа.
Предположим, API принимает необязательное значение базового класса, а вызывающий код располагает необязательным экземпляром подкласса. Запрет такого присваивания вынудил бы разработчика вручную извлекать значение и заново оборачивать его.
Неверное понимание механизма может привести к ожиданию, что преобразование создаёт копию объекта, гарантирует конкретный тип подкласса после передачи или работает в обратную сторону. Обратное преобразование небезопасно: базовый класс может содержать экземпляр другого подкласса.
Пусть Dog является подклассом Animal. Для значения Dog? Swift выполняет логическое преобразование:
none преобразуется в none типа Animal?;some(dog) преобразуется в some(dog), где тот же объект рассматривается как Animal.Преобразование безопасно, поскольку любой Dog является Animal. Для классов это не копирование экземпляра: сохраняется та же ссылка и тот же динамический тип объекта, хотя статический тип переменной становится Animal?.
В обратном направлении Animal? нельзя безусловно присвоить Dog?: внутри может находиться Cat или другой подкласс. Для такого случая требуется проверяемое приведение, а после него результатом будет новый Optional, описывающий успех или неудачу проверки.
Важно отличать это преобразование от изменения значения Optional. Если исходное значение равно nil, объект не появляется. Если оно содержит объект, меняется только тип представления ссылки на уровне статической типизации.
Сервис принимает Animal?, а экран построен для конкретного Dog?. Вариант с ручным извлечением и повторным оборачиванием избыточен: он увеличивает объём кода и создаёт место для ошибочной обработки nil.
Можно сделать API обобщённым, например принимать любой тип-подкласс, но это оправдано только когда сервису действительно нужен конкретный тип и его операции. Универсальный параметр усложняет сигнатуры и иногда ухудшает совместимость с существующим протокольным или классовым API.
Практичное решение — передать Dog? непосредственно как Animal?. Это сохраняет отсутствие значения, не копирует объект и позволяет сервису работать с гарантированным интерфейсом Animal; если позже понадобятся возможности Dog, потребуется отдельное безопасное приведение.
Нет. Для экземпляра класса сохраняется та же ссылка на тот же объект. Преобразование меняет статический взгляд на ссылку с Dog на Animal, поэтому изменения общего ссылочного состояния видны через обе переменные.
Animal? в Dog??Нет, безусловно нельзя: Animal? не сообщает, какой именно подкласс содержится внутри. Условительное приведение может успешно вернуть Dog?, если внутри действительно Dog, и неуспешно завершиться, если там другой объект; отсутствие значения также должно корректно сохраниться как nil.
Нет. Из того, что Dog является подтипом Animal, автоматически не следует, что произвольный Container<Dog> является Container<Animal>. Для пользовательских обобщённых типов безопасность такого преобразования зависит от явно поддерживаемых языком отношений типов и интерфейса контейнера; Optional имеет специальное безопасное преобразование для своего единственного значения.