Как решается конфликт между виртуальной таблицей класса и видимостью протокола в Swift
Что проверяют
Знаете ли вы, как Swift выбирает реализацию метода, когда пересекаются наследование классов и протоколы с реализацией по умолчанию. Обычно следом показывают код, где метод подкласса не вызывается через протокольную переменную, и просят объяснить почему.
Как отвечать
У Swift несколько видов диспатча:
- статический: у структур,
finalиprivateметодов, а также у методов, объявленных только в расширении протокола; - vtable: у переопределяемых методов класса, подкласс подменяет запись в таблице;
- witness table: у требований протокола, своя таблица на каждую пару «тип + протокол»;
- message dispatch: через рантайм Objective-C для
@objc dynamic.
Конфликт возникает так. Класс Parent подписан на протокол Greeter, требование greet() реализовано по умолчанию в extension Greeter, а в самом Parent метода нет. Witness table для Parent: Greeter указывает на реализацию из расширения. Подкласс Child пишет свой greet(), но это не override: переопределять нечего. В итоге let g: Greeter = Child() и g.greet() вызывают версию по умолчанию, а Child().greet() вызывает версию подкласса.
Решение: реализовать greet() в самом Parent. Тогда метод попадает в vtable, witness table ссылается на него, а подкласс делает честный override, который срабатывает и через протокол.
Второй случай: метод есть только в расширении протокола и не объявлен требованием. Он диспатчится статически по типу переменной, и реализация конкретного типа через протокольную ссылку не вызовется. Лечится объявлением метода в протоколе.
Про видимость: у public класса, реализующего public протокол, метод-реализация тоже обязан быть public.
Где спотыкаются
Отвечают «метод класса всегда в приоритете». Через протокольную ссылку это верно, только если класс сам реализует требование.
Выносят подписку в extension Parent: Greeter вместе с реализацией и пытаются переопределить метод в подклассе. Методы из расширений класса не попадают в vtable, и без @objc компилятор не даст сделать override.
Путают witness table с vtable. Протокол не добавляет методы в иерархию класса, у него своя таблица на каждую конформность.