Swift Protocols Cheat Sheet
Covers defining protocols, protocol conformance, protocol extensions with default implementations, and protocol-oriented programming patterns in Swift.
Defining & Conforming
Declaring a protocol and adopting it in multiple types.
protocol Vehicle { var wheels: Int { get } func drive() -> String}struct Car: Vehicle { var wheels: Int = 4 func drive() -> String { "Driving a car with \(wheels) wheels" }}struct Motorcycle: Vehicle { var wheels: Int = 2 func drive() -> String { "Riding a motorcycle" }}let vehicles: [Vehicle] = [Car(), Motorcycle()]for v in vehicles { print(v.drive())}
Protocol Extensions
Providing default implementations shared by all conforming types.
protocol Greetable { var name: String { get }}// Default implementation shared by every conforming typeextension Greetable { func greet() -> String { "Hello, \(name)!" }}struct Person: Greetable { var name: String // greet() comes free from the extension}struct Robot: Greetable { var name: String // Override the default implementation func greet() -> String { "BEEP BOOP, \(name)" }}print(Person(name: "Ana").greet()) // "Hello, Ana!"print(Robot(name: "R2").greet()) // "BEEP BOOP, R2"
Associated Types
Protocols that act as generic templates via associatedtype.
protocol Container { associatedtype Item var items: [Item] { get set } mutating func add(_ item: Item)}struct Stack<T>: Container { var items: [T] = [] mutating func add(_ item: T) { items.append(item) }}var intStack = Stack<Int>()intStack.add(1)intStack.add(2)// Protocols with associated types can't be used as a plain type// (`Container` alone) -- use `some Container` or generics instead.func printCount(_ container: some Container) { print(container.items.count)}
Protocol Composition
Combining and constraining protocols.
- protocol A & B- Composition type requiring conformance to multiple protocols at once
- Protocol inheritance- `protocol B: A { }` requires conformers of B to also conform to A
- class-only protocol- `protocol Delegate: AnyObject { }` restricts conformance to reference types
- some Protocol- Opaque type; a specific concrete type conforming to the protocol, known at compile time
- any Protocol- Existential type (Swift 5.7+); boxes any conforming type, resolved at runtime
- Extension conformance- Types can be retroactively conformed to a protocol via an extension elsewhere
Common Standard Protocols
Protocols from the Swift standard library you'll conform to often.
- Equatable- Enables == comparison; often synthesized automatically for simple structs
- Hashable- Enables use as a Set element or Dictionary key; implies Equatable
- Comparable- Enables <, >, <=, >= and sorting via sort()
- CustomStringConvertible- Provides a custom `description` property used by print() and string interpolation
- Codable- Combines Encodable and Decodable for JSON/plist (de)serialization
- Identifiable- Requires an `id` property; used heavily by SwiftUI's List and ForEach
Existential Boxing & Performance
How `any Protocol` values are stored and why they cost more than generics.
protocol Shape { func area() -> Double}struct Circle: Shape { var radius: Double func area() -> Double { .pi * radius * radius }}// `any Shape` is an existential container: a fixed-size inline buffer// (3 words) plus a pointer to a protocol witness table. Values larger// than the inline buffer are heap-allocated and boxed.let shapes: [any Shape] = [Circle(radius: 2)]// Generic functions specialize per concrete type at compile time --// no boxing, no dynamic dispatch through a witness table.func totalArea<S: Shape>(_ shapes: [S]) -> Double { shapes.reduce(0) { $0 + $1.area() }}// Mixed collections force existentials; homogeneous ones can stay generic.func totalAreaBoxed(_ shapes: [any Shape]) -> Double { shapes.reduce(0) { $0 + $1.area() }}
Conditional Conformance
Making a generic type conform to a protocol only when its parameter does.
struct Box<Content> { var contents: Content}// Box<Content> is only Equatable when Content itself is Equatableextension Box: Equatable where Content: Equatable { static func == (lhs: Box, rhs: Box) -> Bool { lhs.contents == rhs.contents }}// Same technique underlies Array's `Equatable` conformance in the// standard library: `extension Array: Equatable where Element: Equatable`let a = Box(contents: 1)let b = Box(contents: 1)print(a == b) // true, only compiles because Int: Equatable
Static Dispatch Trap in Extensions
A classic gotcha: methods declared only in a protocol extension (not the protocol itself) resolve statically, not dynamically.
protocol Trackable { func log()}extension Trackable { func log() { print("Trackable.log") } // NOT in the protocol requirement list -- resolved statically func extra() { print("Trackable.extra") }}struct Event: Trackable { func log() { print("Event.log") } // overrides via dynamic dispatch func extra() { print("Event.extra") } // shadows, but NOT overridden dynamically}let event = Event()let trackable: Trackable = eventtrackable.log() // "Event.log" -- log() is a protocol requirement, dispatched dynamicallytrackable.extra() // "Trackable.extra" -- extra() isn't a requirement, resolved at compile time by static type
Primary Associated Types (Swift 5.7+)
Constraining `some`/`any` protocol types with generic-like angle-bracket syntax.
protocol Repository<Model> { associatedtype Model func fetchAll() -> [Model]}struct UserRepository: Repository { func fetchAll() -> [String] { ["alice", "bob"] }}// Primary associated type lets you constrain the placeholder directly,// instead of a separate `where` clausefunc printAll(_ repo: some Repository<String>) { repo.fetchAll().forEach { print($0) }}// Also usable with `any`:func handle(_ repo: any Repository<String>) { print(repo.fetchAll().count)}
Advanced Protocol Vocabulary
Terms that come up once you move past basic conformance.
- Witness table- The runtime lookup table mapping a concrete type's methods to a protocol's requirements; backs dynamic dispatch for existentials
- Retroactive conformance- Conforming a type you don't own (e.g. from another module) to a protocol via an extension; risky if two modules both add the same conformance
- Self requirement- A protocol using `Self` in a parameter/return type (e.g. Equatable's ==) can only be used as a generic constraint, not as `any Protocol`
- where clause constraints- `extension Array where Element: Comparable` scopes an extension's methods to only qualifying specializations
- Protocol default + override resolution- Struct/enum methods matching a protocol requirement always win over the extension's default at the protocol-typed call site
- @_marker protocols- Compiler-internal marker protocols like Sendable carry no requirements; conformance is a pure compile-time contract
Favor protocol-oriented programming: define behavior in a protocol extension once, and let value types (structs/enums) conform to it, instead of building a class inheritance hierarchy — it avoids fragile base-class problems and works with Swift's value semantics.