Всегда ли для методов value type в Swift используется статическая диспетчеризация?

Ответ

Нет. Хотя для прямых вызовов методов структуры или перечисления компилятор обычно использует статическую диспетчеризацию (прямой вызов по известному адресу в compile-time), есть случаи применения динамической диспетчеризации.

Когда используется динамическая диспетчеризация:

  1. При работе через протокол (Protocol Witness Table - PWT): Когда экземпляр value type приводится к типу протокола.
  2. При использовании модификатора @objc: Если метод помечен @objc (например, для совместимости с KVO или Objective-C runtime).
  3. При использовании ключевого слова dynamic: Явное указание на необходимость динамической диспетчеризации.

Пример динамической диспетчеризации через протокол:

protocol Renderable {
    func render()
}

struct Button: Renderable {
    // Статическая диспетчеризация при прямом вызове: `Button().render()`
    // Динамическая диспетчеризация при вызове через протокол
    func render() { print("Rendering button") }
}

let renderableItem: Renderable = Button() // Тип — existential контейнер
renderableItem.render() // Диспетчеризация через Protocol Witness Table (PWT)

Итог: Статическая диспетчеризация — это оптимизация по умолчанию для value types, но семантика протоколов и взаимодействие с Objective-C runtime могут переключить ее на динамическую.

Ответ 18+ 🔞

А, слушай, вот это интересный вопрос, блядь! Прям в дебри полезли. Так, короче, смотри.

Вот говорят, мол, структуры и энумы в свифте — они ж на стеке, всё быстро, компилятор умный, диспетчеризация статическая, красота. И в целом — да, ёпта, так и есть. Прямой вызов метода структуры — это как позвать соседа по лестничной клетке: компилятор заранее знает, где он живёт (адрес функции), и тупо стучит в нужную дверь. Никаких поисков, всё на этапе компиляции. Статическая диспетчеризация, её же зовут.

Но жизнь, сука, сложнее, чем кажется! Не всё так однозначно, как в учебнике для распиздяев. Есть моменты, когда наш шустрый value type вдруг начинает плясать под дудку динамической диспетчеризации. То есть искать, какую же функцию вызывать, уже во время работы программы. И вот когда это происходит:

  1. Через протокол, мать его. Это самый частый случай. Стоит тебе запихнуть свою структуру в протокольный тип (экзистенциальный контейнер, этот ваш Renderable), всё, пиздец оптимизации. Компилятор уже не знает наверняка, что там внутри. Он лезет в специальную табличку — Protocol Witness Table (PWT) — и уже оттуда, в рантайме, выковыривает адрес нужной функции для твоего конкретного экземпляра. Динамика, блядь!
  2. Модификатор @objc. Ну это если ты решил потрахаться с легаси-кодом или KVO. Пометил метод этим словом — и понеслась. Свифт начинает использовать Objective-C runtime, а там всё через динамическую диспетчеризацию, через эти ваши селекторы.
  3. Ключевое слово dynamic. Это вообще явный крик: «Эй, компилятор, отъебись со своей оптимизацией, я хочу, чтобы вызов был динамическим!». Часто идёт в паре с @objc, но не всегда.

Вот тебе живой пример, чтобы не быть пиздаболом:

protocol Renderable {
    func render()
}

struct Button: Renderable {
    // Если вызвать `Button().render()` — статика, быстро.
    // А вот если через протокол — будет искать в табличке.
    func render() { print("Рисую кнопку, блядь") }
}

// ВОТ ОН, МОМЕНТ ИСТИНЫ!
let someRenderableShit: Renderable = Button() // Засунули структуру в протокольную переменную
someRenderableShit.render() // Всё, диспетчеризация через PWT, динамическая!

Итог, ёбана: Да, по умолчанию для value types — статика, скорость, оптимизация. Но стоит начать ебаться с протоколами как типами или лезть в Objective-C — всё, добро пожаловать в мир динамического поиска методов. Компилятор — не дурак, он просто даёт тебе выбор: хочешь быстро — пиши прямо, хочешь гибко — используй абстракции. Волшебства, блядь, не бывает.