Как Swift выбирает реализацию требования протокола, если в его расширении есть default implementation, а соответствующий тип объявляет собственную?
Если метод объявлен как требование в теле протокола, реализация типа становится его witness и используется при вызове через протокол или generic-ограничение. Default implementation из расширения применяется только тогда, когда тип не предоставил собственную реализацию.
Протоколы должны были позволять описывать общий контракт без принуждения каждого типа дублировать базовое поведение. Поэтому Swift поддерживает default implementation в расширениях протоколов: она закрывает обязательное требование, но не отменяет возможность переопределить его на уровне соответствующего типа.
Для вызовов через протокол или generic-код компилятор использует сведения о соответствии типа протоколу. В упрощённой модели это таблица witness-функций, где для каждого требования выбирается конкретная реализация.
Ошибочно считать, что наличие метода в расширении протокола всегда делает его реализацию приоритетной. Это зависит от того, объявлен ли метод требованием протокола или существует только как дополнительный член расширения.
Если выбран не тот механизм, вызов через значение протокольного типа может использовать default implementation, хотя у конкретного типа визуально есть одноимённый метод. Такое поведение особенно опасно в архитектурах с generic-кодом и type erasure: результат зависит не от фактического типа, а от способа, которым значение передали в функцию.
Когда метод присутствует в теле протокола, он является требованием. При объявлении соответствия Swift пытается сопоставить ему метод типа; если такой метод найден, именно он записывается как witness. Если метод отсутствует, witness берётся из default implementation расширения протокола.
Поэтому вызов через any Renderable, через параметр с ограничением T: Renderable или через конкретный тип обращается к одной выбранной реализации требования. Собственная реализация типа не является «переопределением» расширения в классическом смысле: она заменяет default witness при формировании соответствия.
Здесь render объявлен в протоколе, поэтому Card.render() становится реализацией требования. show получает existential-значение, но вызывает сохранённый witness, то есть возвращает card.
Это отличается от метода, который объявлен только в расширении протокола и не указан в его теле. Такой метод не является требованием, не попадает в таблицу соответствия и обычно выбирается статически по известному типу выражения. Поэтому одинаковое имя само по себе не гарантирует полиморфный вызов.
В SDK есть протокол форматирования с общей реализацией для большинства моделей. Для одной модели требуется специальный формат, и разработчик добавляет одноимённый метод в сам тип. Если метод является требованием протокола, вызовы через generic-сервис и через any-значение используют специальную реализацию без дополнительных условий.
Рассматривались два варианта. Первый — оставить метод только в расширении: это короче, но не создаёт требования и может привести к статической диспетчеризации. Второй — объявить метод в теле протокола и предоставить default implementation в расширении: это делает поведение частью контракта, но добавляет требование всем conforming-типам.
Выбран второй вариант. Он обеспечивает единый полиморфный результат для конкретных типов, generic-кода и existential-значений. В итоге специальная модель форматируется корректно независимо от места вызова, а остальные модели используют общий default witness.
Да. Default implementation закрывает требование так же, как метод, написанный в самом типе. Соответствие не становится условным или неполным: тип получает полноценную конформность, а вызов требования использует witness из расширения протокола.
Да, если этот метод действительно совпадает с требованием по имени, параметрам, возвращаемому типу и ограничениям. Он должен участвовать в формировании соответствия типа; после этого вызовы требования через протокол используют реализацию типа, а не default implementation.
Он не станет требованием и не будет частью witness-таблицы. Вызов такого метода определяется статическим типом выражения и может выбрать реализацию из расширения даже при наличии одноимённого метода у конкретного типа. Чтобы получить полиморфное поведение, метод нужно объявить в теле протокола как требование.