В чем разница между статической и динамической диспетчеризацией в Swift?

«В чем разница между статической и динамической диспетчеризацией в Swift?» — вопрос из категории Swift Core, который задают на 22% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Диспетчеризация — это механизм определения того, какая реализация метода должна быть вызвана. В Swift есть два основных типа.

Определения

  • Статическая диспетчеризация (Static Dispatch, раннее связывание): Решение о вызываемом методе принимается во время компиляции. Компилятор знает точный адрес функции и может выполнить прямой вызов или даже встраивание (inline).
  • Динамическая диспетчеризация (Dynamic Dispatch, позднее связывание): Решение принимается во время выполнения программы. Используется таблица (например, Virtual Table для классов или Witness Table для протоколов) для поиска нужной реализации.

Сравнение

Характеристика Статическая диспетчеризация Динамическая диспетчеризация
Время разрешения Время компиляции. Время выполнения (Runtime).
Скорость Быстрее. Нет накладных расходов на поиск в таблице, возможна оптимизация. Медленнее. Требуется косвенный вызов через таблицу.
Полиморфизм Не поддерживает полиморфное поведение. Поддерживает полиморфизм (вызов метода зависит от фактического типа объекта).
Гибкость Менее гибкая. Более гибкая, основа ООП.
Типы в Swift Структуры (struct), перечисления (enum), final классы и методы, глобальные функции. Не-final классы и их методы, вызовы через протоколы.

Примеры кода

// 1. СТАТИЧЕСКАЯ ДИСПЕТЧЕРИЗАЦИЯ (на примере структуры)
struct Calculator {
    // Для структур компилятор использует статическую диспетчеризацию.
    func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
}
let calc = Calculator()
let result = calc.add(5, 3) // Компилятор ЗНАЕТ, что вызовется Calculator.add

// 2. ДИНАМИЧЕСКАЯ ДИСПЕТЧЕРИЗАЦИЯ (на примере классов)
class Animal {
    func speak() { print("...") } // Метод попадает в Virtual Table класса Animal
}
class Dog: Animal {
    override func speak() { print("Woof!") } // Метод попадает в Virtual Table класса Dog
}

let myPet: Animal = Dog() // Статический тип Animal, динамический (реальный) - Dog
myPet.speak() // В RUNTIME система смотрит в vtable объекта Dog и вызывает Dog.speak() -> "Woof!"

// 3. ДИНАМИЧЕСКАЯ через протокол (Witness Table)
protocol Drawable {
    func draw()
}
struct Circle: Drawable {
    func draw() { print("Drawing a circle") }
}
let shape: Drawable = Circle() // Тип протокола
shape.draw() // В RUNTIME вызов через Witness Table для Circle -> "Drawing a circle"

Практический совет: Используйте final для классов и методов, которые не планируется переопределять, чтобы компилятор мог применить статическую диспетчеризацию и оптимизации, повышая производительность.