Protocols: defining a contract
Unit 4 · Protocols & generics. This is the abstraction unit — how Swift lets many types share a shape without sharing a base class. It underpins the whole standard library.
A protocol is a contract. It lists requirements — properties, methods — that a conforming type promises to provide, and the compiler enforces the promise. Protocols are how Swift says "any type that can do X," which turns out to be a more flexible tool than inheritance for almost everything you'll model.
Declaring a protocol
protocol Vehicle { var wheelCount: Int { get } // a readable property var name: String { get set } // readable and writable func describe() -> String // a method requirement }
A property requirement states its access with { get } (readable) or { get set }
(readable and writable). A method requirement is just a signature — no body. The protocol
says what, never how.
Conforming to it
struct Bicycle: Vehicle { let wheelCount = 2 // a `let` satisfies a `{ get }` requirement var name: String func describe() -> String { "\(name) has \(wheelCount) wheels" } }
Writing struct Bicycle: Vehicle declares the conformance; the compiler then checks that
every requirement is met. Miss one and it won't build — the contract is verified at compile
time, not hoped for at runtime. Note that a let wheelCount satisfies { get }, while a
{ get set } property needs a var.
Using a protocol as a type
The payoff: you can write code against the protocol and stay ignorant of the concrete type.
func report(_ vehicle: Vehicle) -> String { vehicle.describe() } let fleet: [Vehicle] = [Bicycle(name: "Cruiser"), Bicycle(name: "Tandem")] for v in fleet { print(v.describe()) }
report accepts anything that conforms to Vehicle. An array typed [Vehicle] can hold any
mix of concrete types, as long as each is a Vehicle. You program to the contract, and any
conformer slots in.
A Swift protocol maps closely to a Kotlin interface. The differences you'll feel: Swift
protocols can require properties (not just methods), can be adopted by value types
(struct/enum), and — as you'll see next lesson — can ship default implementations via
extensions rather than needing an abstract base class.
Composing protocols with &
A parameter can require conformance to several protocols at once:
protocol Named { var name: String { get } } protocol Aged { var age: Int { get } } func greet(_ person: Named & Aged) -> String { "\(person.name), age \(person.age)" }
Named & Aged is a protocol composition: it means "a value that is both." This keeps
protocols small and single-purpose — you combine them at the point of use instead of building
one giant protocol that demands everything.
Keep protocols small. A protocol should describe one capability. Named, Equatable,
Identifiable each say one thing, and you compose them with & where a function needs
more. Small protocols conform easily and read clearly; a kitchen-sink protocol is painful to
adopt and hard to reuse.
Your turn
Define a conformance and use the protocol as a type.
Knowledge check
Q: What does { get } versus { get set } mean on a protocol property?
{ get } requires the property be readable — a let, a var, or a computed getter all
satisfy it. { get set } requires it be readable and writable, so a var (stored or
computed with a setter) is needed.
Q: What does Named & Aged express as a parameter type?
Protocol composition: the value must conform to both Named and Aged. It lets you keep
protocols small and combine capabilities only where a function actually needs them.