Как происходит вызов метода и определение реализации через таблицы диспетчеризации в Swift
Что проверяют
Понимаете ли вы, что в Swift есть три способа вызвать метод и какой из них компилятор выберет в конкретном месте. Следом обычно дают код с протоколом и расширением и спрашивают, что напечатается.
Как отвечать
Назовите три вида диспетчеризации:
- статическая: адрес функции известен при компиляции, возможен инлайнинг. Так вызываются методы структур и перечислений,
finalиprivateметоды классов, методы из расширений; - табличная: у каждого класса есть vtable, массив указателей на реализации. Подкласс получает копию таблицы родителя и заменяет в ней переопределённые методы. Вызов это чтение метаданных типа, переход по известному смещению и вызов по указателю. Для протоколов аналог называется witness table, она своя у каждой пары «тип и протокол» и работает при вызове через
any Pили дженерик; - message dispatch: механизм Objective-C через
objc_msgSend, включается для@objc dynamic. Только он позволяет подменять реализацию в рантайме и нужен для KVO.
Важно: vtable не ищет метод вверх по иерархии. Поиск с кешем делает objc_msgSend, а в таблице позиция метода известна заранее, поэтому вызов стоит одно косвенное обращение.
Классическая ловушка:
protocol Greeter { func hello() }
extension Greeter {
func hello() { print("default") }
func bye() { print("bye default") }
}hello объявлен в протоколе, попадает в witness table и переопределяется типом. bye есть только в расширении и вызывается статически: через переменную any Greeter всегда сработает версия из расширения, даже если у типа есть свой bye.
Компилятор умеет убирать косвенность: final, whole module optimization и специализация дженериков превращают табличные вызовы в прямые.
Где спотыкаются
Описывают поиск реализации по суперклассам. Это поведение Objective-C runtime, а не vtable.
Путают @objc и dynamic: @objc делает метод видимым для Objective-C, но вызов через сообщения гарантирует только dynamic.
Называют final прежде всего оптимизацией. Он в первую очередь запрещает переопределение, а выигрыш в скорости заметен разве что в горячих циклах.