SwiftQA Random

How does protocol dispatch differ from dynamic dispatch?

Protocol dispatch (via witness tables) is a form of dynamic dispatch, alongside vtable dispatch and message dispatch.

The three (really four) dispatch mechanisms in Swift

1. Static dispatch (direct call) — compiler knows exactly which function to call; applies to structs, final classes/methods, free functions, specialized generics.

2. Table dispatch — vtables (class inheritance) — non-final methods resolve at runtime via a per-class-hierarchy vtable.

class Shape { func area() -> Double { 0 } }
class Circle: Shape { override func area() -> Double { ... } }
let s: Shape = Circle()
s.area()  // vtable dispatch

3. Table dispatch — witness tables (protocols) — each protocol conformance gets its own witness table.

protocol Shape { func area() -> Double }
struct Circle: Shape { func area() -> Double { ... } }
func render<T: Shape>(_ shape: T) { shape.area() }  // witness table dispatch (unless specialized)

4. Message dispatch (objc_msgSend@objc dynamic) — the most expensive, but the only one supporting runtime method swizzling, KVO.

The key structural difference

Vtables model inheritance (a linear hierarchy); witness tables model conformance (a type can satisfy arbitrarily many unrelated protocols, each with its own independent table).

Vtable (class) dispatchWitness table (protocol) dispatch
Table scopePer class, inherited/overriddenPer protocol conformance
Resolved viaObject’s vtable pointerWitness table passed as hidden param or bundled in existential
Supports multiple unrelated conformances?NoYes

Extension methods — a common gotcha

Methods added via extension that aren’t part of the protocol’s requirement list are statically dispatched, even called on a protocol-typed value:

protocol Greetable { func name() -> String }
extension Greetable {
    func greet() -> String { "Hello, \(name())" }         // in witness table
    func loudGreet() -> String { greet().uppercased() }    // NOT a requirement — static dispatch
}

struct Person: Greetable {
    func name() -> String { "Alice" }
    func loudGreet() -> String { "OVERRIDE" }  // does NOT override the extension version
}

let p: any Greetable = Person()
p.loudGreet()  // calls the EXTENSION's loudGreet(), not Person's

One-liner if pressed: “Protocol dispatch via witness tables is itself a form of dynamic dispatch, structurally similar in cost to class vtable dispatch, but witness tables model per-conformance dispatch rather than an inheritance hierarchy. The real fork that matters for performance is static vs. dynamic: a protocol method call is statically dispatched and fully inlinable when the compiler can specialize, and falls back to witness-table indirection only when the type is genuinely unknown at compile time.”