Skip to content
Protocol Extensions and POP
step 1/7

Reading — step 1 of 7

Learn

~3 min readProtocol-Oriented Programming

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 extension with inheritance — 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…