Static vs dynamic dispatch in Swift?
Predict first
Without whole-module optimisation, which call is guaranteed to be a direct call?
final class Engine { func start() {} }
class Car { func drive() {} }
Engine().start() // A
Car().drive() // B
- A only
- B only
- Both
- Neither
Mental model. Static dispatch is a printed address on an envelope. Dynamic dispatch is calling directory assistance every time.
Static — the compiler knows the exact implementation: structs, enums, generics after specialization, final classes, and protocol extension methods that are not requirements. It can inline and optimize.
Dynamic — the implementation is chosen at runtime:
- Class methods that can be overridden → vtable.
@objc/dynamic→ message send.- Protocol methods through
any P→ witness table.
Dynamic dispatch is more flexible; static is faster and inlinable.
final rules out overrides, so A is a direct call. B goes through the class vtable unless whole-module optimisation proves no subclass exists.
Common traps
- “Protocols are always dynamic.” A generic constrained to a protocol is specialized statically. Only the existential path uses the witness table.
- “
finalis only a style preference.” It changes dispatch, and therefore optimization, at every call site.
They might follow up with
- What is a vtable? A per-class table of method pointers; subclassing replaces entries, so lookup is dynamic.
- How do you see what the compiler chose? Emit SIL and look for
class_method,witness_method, orfunction_ref.
Related questions
- 1.11.2 Why does final (and whole-module optimization) help performance?
- 1.11.3 You added a method in a protocol extension and overrode it on a concrete type, but calling through any P does…
- 1.4.6 What is an existential container? What is a witness table?
Why does final (and whole-module optimization) help performance? →