В Swift, когда используется таблица виртуальных методов (Virtual Method Table или vtable)?

Ответ

Таблица виртуальных методов (vtable) используется Swift компилятором для реализации динамической диспетчеризации (dynamic dispatch) вызовов методов в иерархии классов.

Когда это происходит:

  • При вызове нефинализированного (non-final) метода на экземпляре класса, объявленном через тип его родителя (полиморфизм).
  • Когда метод может быть переопределён (override) в подклассе.

Пример:

class Animal {
    func makeSound() { print("Some sound") } // Попадёт в vtable
}

class Dog: Animal {
    override func makeSound() { print("Bark") } // Переопределяет запись в vtable
}

class Cat: Animal {
    override func makeSound() { print("Meow") } // Переопределяет запись в vtable
}

let animals: [Animal] = [Dog(), Cat()]
for animal in animals {
    animal.makeSound() // Динамический вызов через vtable: "Bark", "Meow"
}

Когда vtable НЕ используется (статическая диспетчеризация):

  1. Для final классов и методов.
  2. Для private методов, если они не могут быть переопределены.
  3. Для вызовов на конкретном типе (например, let dog = Dog(); dog.makeSound()), где компилятор может определить точный метод на этапе компиляции.
  4. Для структур (struct) и перечислений (enum), которые не поддерживают наследование.

Накладные расходы: Динамическая диспетчеризация добавляет один дополнительный шаг — косвенный вызов через указатель в таблице — что может незначительно влиять на производительность в критичных к скорости местах.

Ответ 18+ 🔞

А, ну вот, опять эти ваши виртуальные таблицы, ёпта! Сидит компилятор Swift, такой весь из себя умный, и думает: "Как бы мне так сделать, чтобы когда ты вызываешь метод у какого-нибудь Animal, а на самом деле там Dog сидит, чтобы всё равно правильный лай прозвучал, а не какое-то общее животное мычание?"

И придумал он, хитрая жопа, vtable — табличку такую специальную. Представь себе меню в столовой, где против каждого номера блюда написано: "первое — борщ", "второе — котлета". Так вот vtable — это меню для методов класса. Только в этом меню против пункта makeSound у Animal изначально написано "Some sound", а у Dog — уже "Bark" перечёркивает.

Когда вся эта цирковая возня с таблицей включается:

  • Когда ты вызываешь метод, который НЕ final.
  • Когда объект объявлен как родитель (Animal), но на самом деле он — ребёнок (Dog). То есть самый что ни на есть полиморфизм, его величество.

Смотри, как это выглядит вживую:

class Animal {
    func makeSound() { print("Some sound") } // Эта функция записывается в vtable как стандартный рецепт
}

class Dog: Animal {
    override func makeSound() { print("Bark") } // А эта — перезаписывает рецепт в таблице! Теперь тут "Bark"
}

class Cat: Animal {
    override func makeSound() { print("Meow") } // И тут своё, блядь, "Meow"
}

let animals: [Animal] = [Dog(), Cat()] // В массиве с типом "Animal" сидят и собака, и кошка
for animal in animals {
    animal.makeSound() // Компилятор, ебать, не знает на этапе компиляции, кто тут кто! Он лезет в vtable объекта, смотрит: "Ага, у тебя в меню против makeSound что? А, 'Bark'! Окей, выполняем 'Bark'." И так для каждого.
}

А когда этой хуйни с таблицами НЕ происходит (и слава богу, потому что быстрее)?

  1. Если класс или метод помечен как final. Всё, приехали. Наследование и переопределение запрещены. Компилятор сразу знает, какую функцию вызывать — статически, без всяких там меню.
  2. Для private методов, которые снаружи не видно и переопределить их не выйдет.
  3. Когда ты вызываешь метод на конкретном, известном типе. let sharik = Dog(); sharik.makeSound() — тут и ежу понятно, что лай будет. Зачем таблицу-то искать?
  4. Для структур (struct) и перечислений (enum). Они, блядь, вообще не поддерживают наследование, так что никакого полиморфизма, никаких виртуальных таблиц. Всё решается на этапе компиляции.

Цена вопроса: Да, этот динамический вызов через таблицу — он чуть медленнее. Один лишний шаг: найти таблицу, найти в ней нужный адрес метода, перейти по нему. В 99% случаев похуй, но если у тебя там в самом ядре цикла миллионы вызовов крутятся, то может и почувствоваться. Поэтому для таких мест final — твой друг, товарищ и брат.