Когда создается таблица виртуальных методов и протокольная таблица в Swift
Что проверяют
Понимаете ли вы, как Swift выбирает реализацию метода при вызове, и что таблицы принадлежат типу, а не каждому объекту. Следом почти всегда идёт вопрос про метод из расширения протокола, который «не переопределяется».
Как отвечать
Обе таблицы в основном генерирует компилятор, и они лежат в бинарнике как статические данные. Рантайм достраивает их только для обобщённых типов.
- vtable класса: часть метаданных класса, по слоту на каждый метод, который можно переопределить. У подкласса своя таблица, где слот переопределённого метода указывает на новую реализацию.;
- когда vtable не нужна: методы
final,private,fileprivate, а при whole module optimization иinternalбез наследников вызываются напрямую. Методы из расширений класса в vtable не попадают, и переопределить их можно только с@objc, тогда вызов идёт черезobjc_msgSend; - witness table: одна на пару «тип и протокол», в ней адреса реализаций требований протокола. Для обычного соответствия её эмитит компилятор, а для обобщённых типов и условных соответствий рантайм собирает её при первом использовании и кэширует. С метаданными обобщённых классов так же: vtable для
Box<Int>появляется при первом обращении к этому типу; - где работает witness table: при вызове через экзистенциал
any P(контейнер хранит значение, метаданные и указатель на таблицу) и в неспециализированной обобщённой функции, куда таблица приходит скрытым параметром. После специализации вызов становится прямым.
Есть ещё value witness table: она описывает, как копировать, перемещать и уничтожать значение типа, и нужна любому обобщённому коду.
Где спотыкаются
Говорят, что таблица создаётся при создании объекта или «обновляется» при переопределении. Таблицы статичны и существуют на тип, а не на экземпляр.
Не знают, что метод, объявленный только в расширении протокола, в witness table не входит. Через any P вызовется реализация из расширения, даже если у типа есть свой метод с тем же именем.
Считают, что наследник NSObject автоматически использует диспетчеризацию Objective-C. Swift-методы такого класса по-прежнему идут через vtable, если не помечены @objc dynamic.