Что можно делать в расширениях протоколов в Swift
Что проверяют
Понимаете ли вы разницу между требованием протокола и методом из его расширения и как Swift выбирает реализацию при вызове. Почти всегда следом дают задачку: метод есть и в расширении, и в самом типе, что напечатает вызов через переменную типа протокола.
Как отвечать
В расширении протокола можно:
- дать реализацию по умолчанию методам, вычисляемым свойствам и сабскриптам из требований. Тип получает их бесплатно и может заменить своей версией;
- добавить новые методы и вычисляемые свойства поверх требований. Так устроена почти вся стандартная библиотека:
map,contains,first(where:)живут в расширенияхSequenceиCollection; - добавить инициализатор, если он делегирует в
self.init(...)из требований; - ограничить расширение условием:
extension Collection where Element: Numericилиextension Presentable where Self: UIViewController. Методы появятся только у подходящих типов.
Чего нельзя: хранимых свойств (памяти под них у протокола нет), новых требований (объявленное только в расширении требованием не становится) и наследования в заголовке: extension P: Q для протокола не компилируется.
Главное, что стоит проговорить, это диспетчеризация. Метод, объявленный в самом протоколе, вызывается динамически через witness table, и срабатывает реализация конкретного типа. Метод, существующий только в расширении, выбирается статически по типу переменной: через let x: any P = MyType() вы получите версию из расширения, даже если в MyType есть своя.
Где спотыкаются
Говорят, что в расширении можно объявить обычное свойство. Можно только вычисляемое; для хранения нужно требование в самом протоколе, а associated objects через рантайм Objective-C работают только с классами.
Не знают про статическую диспетчеризацию и уверенно отвечают, что вызов через протокол всегда берёт реализацию типа. Это ровно та ловушка, ради которой задают вопрос.
Путают реализацию в типе с переопределением. У структур override нет, а у классов своя грабля: если базовый класс взял реализацию по умолчанию, одноимённый метод подкласса при вызове через протокол не сработает, потому что witness table построена для базового класса.