При обращении к статическому требованию протокола через generic-параметр, откуда берётся конкретная реализация?
Конкретная реализация статического требования берётся из таблицы соответствия протоколу для фактического типа, переданного вместо generic-параметра. Поэтому generic-код вызывает реализацию, которую тип предоставил как witness, включая реализацию по умолчанию из расширения протокола, если тип не объявил собственную.
Это отличается от статического метода, который существует только в extension протокола и не объявлен его требованием: такой метод выбирается статически по видимому ограничению, а не через таблицу соответствия.
Протоколы в Swift позволяют описывать общий контракт, не связывая код с конкретным типом. Для generic-кода этого недостаточно: программе нужно не только знать, что тип соответствует протоколу, но и уметь вызвать конкретную реализацию требования во время выполнения.
Таблица соответствия протоколу решает эту задачу. Она связывает каждое требование протокола с реализацией конкретного типа и позволяет сохранять статический полиморфизм без ручной проверки типа.
Рассмотрим generic-функцию, принимающую метатип T.Type. Ей неизвестен конкретный тип T, но она должна вызвать его статическое свойство через ограничение протоколом.
Важно различать два случая:
Если перепутать эти случаи, собственная реализация типа может не вызываться, хотя её имя совпадает с именем метода или свойства из extension.
Когда статический член объявлен требованием протокола, соответствие типа формирует запись для этого требования. Если тип реализует член самостоятельно, в таблицу попадает его реализация. Если тип не реализует требование, но доступна реализация по умолчанию из extension, в таблицу попадает default implementation.
При вызове через generic-параметр Swift использует ограничение T: Протокол, получает таблицу соответствия для фактического T и вызывает связанную с требованием реализацию. Это позволяет разным типам иметь разное поведение в одной generic-функции.
Для User generic-функция использует его собственное свойство, а для Guest — реализацию из extension. Ключевым является то, что label объявлено в самом протоколе.
Если же член существует только в extension протокола и не является требованием, он не попадает в таблицу соответствия. Вызов такого члена через generic-ограничение связывается с реализацией extension; одноимённый статический член конкретного типа не переопределяет её полиморфно.
Главный компромисс — необходимость заранее включить в протокол все члены, для которых требуется динамический выбор реализации. Добавление члена только в extension удобно для вспомогательного поведения, но не создаёт нового полиморфного требования.
В библиотеке есть generic-функция, которая строит метку для типа модели. Для большинства моделей подходит стандартная метка, но отдельные модели должны возвращать локализованное или доменное имя.
Первый вариант — добавить статическое свойство только в extension протокола. Он прост, но generic-код всегда будет ориентироваться на реализацию extension, поэтому специализированная метка типа не станет частью полиморфного поведения.
Второй вариант — объявить свойство требованием протокола и предоставить default implementation. Это требует изменить контракт, зато каждая модель может заменить значение, а generic-функция корректно использует реализацию фактического типа.
Выбирается второй вариант: обязательное статическое требование с реализацией по умолчанию. В результате стандартное поведение сохраняется, а специализированные типы получают предсказуемое переопределение без проверки конкретных типов и без условных ветвлений.
1. Что произойдёт, если тип объявит одноимённое статическое свойство, но это свойство отсутствует в протоколе?
Generic-код, ограниченный этим протоколом, не обязан использовать свойство конкретного типа. Если имя доступно только из extension протокола, вызывается реализация extension. Собственный одноимённый член типа может быть доступен при обращении непосредственно через конкретный тип, но он не становится witness для протокола.
2. Обязательно ли generic-функции знать конкретный тип во время написания?
Нет. При написании функции известен только generic-параметр и его протокольные ограничения. При конкретном вызове Swift связывает T с фактическим типом и передаёт в generic-код соответствующую таблицу соответствия, поэтому вызов требования остаётся типобезопасным.
3. Чем отличается вызов требования через generic-параметр от вызова через конкретный тип?
Через конкретный тип Swift напрямую обращается к его статическому члену. Через generic-параметр выбор происходит с учётом протокольного контракта: используется реализация, зарегистрированная для требования в таблице соответствия. Поэтому generic-вызов поддерживает полиморфизм только для тех членов, которые явно объявлены требованиями протокола.