What happens at compile time vs runtime in Swift?
Compile time
- Type checking and inference
- Static/direct dispatch resolution
- Generic specialization (when possible)
- Sendable / isolation checking
- Memory layout decisions for concrete, non-resilient types
- Witness table and vtable construction
@objcbridging surface generation#selector,#file,#lineliterals- ABI-level calling conventions (resilient vs
@frozen)
Runtime
- Dynamic dispatch resolution — the actual choice of which function pointer to call
- ARC retain/release counting
- Copy-on-write triggering (
isKnownUniquelyReferenced) - Existential container behavior — which concrete type is currently held
- Actor serialization / task scheduling
- Resilient type field access via runtime accessors
- Objective-C message dispatch (
objc_msgSend) - Type metadata queries (
is,as?,type(of:), reflection)
The genuinely blurry middle ground
- Generic code compiled unspecialized — compile-time decision, runtime witness table lookup.
@frozenvs resilient — compile-time decision that determines the shape of runtime behavior for every client forever.- Reentrancy in actors — isolation rules enforced at compile time, but manifestation depends on runtime task interleaving.
A framework for answering this live
Test: does this depend on the concrete runtime state of the program, or is it purely a function of the source code’s static shape?
Summary table
| Compile time | Runtime |
|---|---|
| Type checking, generic constraint verification | ARC retain/release counting, deinit timing |
| Sendable/isolation checking | Task scheduling, actor serialization |
| Static dispatch resolution, generic specialization | Vtable/witness-table lookup result |
| Layout decisions for concrete/frozen types | COW uniqueness checks, existential boxing |
| Witness/vtable table construction | objc_msgSend method lookup for dynamic |
@objc bridging surface generation | Resilient type field access |
#selector/compiler literals | is/as?/type(of:) runtime queries |
One-liner if pressed: “Swift resolves as much as possible statically — types, generic specialization, Sendable/isolation safety, static dispatch, memory layout for concrete types — and defers to runtime only what genuinely depends on execution state: which concrete type an existential holds, reference counts and COW uniqueness, and task/actor scheduling.”