Protocols
Describing what a type can do rather than what it is — the abstraction Swift is actually built around.
By the end you will be able to
- Define a protocol and conform types to it
- Use a protocol as a type, and explain what that costs at runtime
- Explain why Swift prefers protocol conformance to class inheritance
A struct cannot inherit from another struct. So how does Swift let a Circle and a Square share behaviour?
A protocol is a contract
protocol Shape {
var area: Double { get }
var name: String { get }
func scaled(by factor: Double) -> Self
}
It declares requirements and no implementation. { get } means readable; { get set } means readable and writable. A conforming type has to satisfy every one.
struct Circle: Shape {
var radius: Double
var area: Double { Double.pi * radius * radius }
var name: String { "circle" }
func scaled(by factor: Double) -> Circle {
Circle(radius: radius * factor)
}
}
The compiler checks the conformance. Miss a requirement and you get an error naming exactly what is absent.
Three unrelated struct values sitting in one array, each running its own implementation. No inheritance anywhere.
Conformance can be added later, and elsewhere
This is the part that has no equivalent in class inheritance. You can make a type you did not write conform to a protocol you did:
protocol Describable {
var summary: String { get }
}
extension Int: Describable {
var summary: String { "the number \(self)" }
}
extension Array: Describable {
var summary: String { "\(count) items" }
}
You cannot retrofit a superclass onto Int. You can retrofit a protocol. The difference is what each would have to touch: a superclass is part of a type's identity and memory layout, so bolting one onto Int would mean changing the shape of every Int already compiled into every framework on the device. A conformance touches none of that — it files a lookup table ("here is how Int does describe()") alongside the untouched type. This is why so much of Swift's own API surface is protocols — Equatable, Hashable, Comparable, Codable, Collection — and why third-party types slot into the language's machinery so cleanly.
Protocols can require initializers and static members
protocol Defaultable {
static var empty: Self { get }
init()
}
Self (capital S) means "the conforming type". In Circle, Self is Circle.
Using a protocol as a type
let shapes: [Shape] stores values of different concrete types in one array. That is an existential — a box that hides the real type behind the protocol.
Since Swift 5.6 you can, and increasingly should, write it explicitly:
let shapes: [any Shape] = [Circle(radius: 1), Rectangle(width: 2, height: 3)]
The any is not decoration. It marks a real cost: because the concrete type is unknown at compile time, each call goes through a lookup table, values may be boxed, and the compiler cannot specialise or inline. For a handful of shapes this is irrelevant; in a tight loop over thousands it is not.
The alternative is generics, which keep the concrete type and pay nothing — that is the next-but-one lesson.
Protocol inheritance and composition
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
protocol Person: Named, Aged { } // inherits both requirements
func greet(_ someone: Named & Aged) { } // composition, no new protocol needed
& composes on the spot. Prefer several small protocols over one large one — a type should only have to promise what a given function actually needs.
Protocol conformance is checked at compile time but dispatched at runtime. What does this print?
protocol Greeter {
func greet() -> String
}
struct English: Greeter {
func greet() -> String { "Hello" }
}
struct French: Greeter {
func greet() -> String { "Bonjour" }
}
func welcome(_ greeter: Greeter) -> String {
greeter.greet() + "!"
}
let greeters: [Greeter] = [English(), French(), English()]
for greeter in greeters {
print(welcome(greeter))
}Why Swift prefers this to inheritance
Class inheritance couples you to a hierarchy. You get exactly one superclass, chosen up front, and you inherit all of its state whether you want it or not. Deep hierarchies become hard to change because every subclass depends on details of every ancestor.
Protocols decouple the capability from the hierarchy:
- A type can conform to as many protocols as it likes.
- Conformance can be added after the fact, in a different module.
- Value types can participate, so you keep value semantics.
- Each protocol is small, so a function can ask for exactly what it needs.
What does the any in [any Shape] tell you?
Define and conform
Define a Payable protocol requiring grossPay and description, and conform two different types to it.
Retrofit conformance
Make Int and String conform to a Sized protocol, then report the size of a mixed array.
Compose small protocols
Write introduce(_:) that accepts anything which is both Named and Aged, without declaring a third protocol.
"Protocol-oriented programming" is a phrase Apple used heavily. In your own words, explain what problem with class hierarchies it is reacting to — and give one situation where a class hierarchy is still the better answer.
You can now:
- Define protocols with property, method and initializer requirements
- Conform any type, including ones you did not write
- Explain what
anycosts and when a mixed collection justifies it - Compose small protocols with
&
Next up: protocol extensions — where protocols stop being contracts and start carrying real behaviour.