Reading — step 1 of 7
Learn
Protocol-Oriented Programming (POP) is one of Swift's most distinctive design philosophies. Apple's WWDC 2015 talk "Protocol-Oriented Programming in Swift" laid out the case: prefer protocols + extensions over class inheritance for code reuse and polymorphism.
Why protocols over inheritance
Class inheritance has well-known problems: deep hierarchies, fragile base classes, the diamond problem, awkward sharing across unrelated types. Protocols sidestep all of that.
Protocol extensions provide default implementations
protocol Greetable {
var name: String { get }
func greet() -> String
}
extension Greetable {
func greet() -> String {
return "Hello, \(name)!"
}
}
struct Person: Greetable {
let name: String
// greet() inherited from extension
}
struct ShoutyPerson: Greetable {
let name: String
func greet() -> String { // override the default
return "HELLO, \(name.uppercased())!"
}
}
Conformers get the default for free; they can override when they need different behavior.
Where clauses on extensions
Add methods only to specific specializations:
extension Array where Element: Numeric {
func sum() -> Element {
reduce(0, +)
}
}
[1, 2, 3].sum() // 6
[1.5, 2.5].sum() // 4.0
// ["a", "b"].sum() // ✗ compile error — String isn't Numeric
The Swift standard library uses this heavily — Array.sorted() only exists when Element is Comparable, etc.
Composition over inheritance
protocol Drawable {
func draw()
}
protocol Tappable {
func tap()
}
// A type can conform to MANY protocols, unlike single-inheritance classes:
struct Button: Drawable, Tappable {
func draw() { print("drawing") }
func tap() { print("tapped") }
}
Multiple conformance is uncomplicated — no diamond problem, no super calls.
Protocol composition with &
A single type constraint that's the intersection of multiple protocols:
func render(item: Drawable & Tappable) {
item.draw()
item.tap()
}
render(item: Button()) // Button conforms to both — works
Associated types
Protocols can have placeholder types:
protocol Container {
associatedtype Item
mutating func add(_ item: Item)
func get(at index: Int) -> Item
var count: Int { get }
}
struct IntStack: Container {
typealias Item = Int // explicit, or let inference figure it out
private var items: [Int] = []
mutating func add(_ item: Int) { items.append(item) }
func get(at index: Int) -> Int { items[index] }
var count: Int { items.count }
}
Associated types are like generics for protocols — let one protocol describe many concrete types with related-but-different members.
When NOT to use POP
- When you need shared MUTABLE state across instances (classes do that better than protocols+extensions on structs).
- When the type is naturally identity-based, not value-based (a network socket, a UI widget).
- When inheritance fits the domain model cleanly (Animal → Dog → Labrador).
Common mistakes
- Adding stored properties in extensions — not allowed. Extensions add methods/computed-properties only.
- Trying to override extension methods at runtime — extension methods aren't dynamically dispatched (unless declared
dynamic). The static type determines which is called. - Confusing
extensionwithinheritance— extension adds to an existing type or protocol; doesn't create a subtype.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…