Protocols

19.1 Defining and Adopting Protocols

protocol Shape {
    var name: String { get }              // read-only property requirement
    var area: Double { get }
    func describe() -> String
    static var sides: Int { get }
    init(size: Double)                    // initializer requirement
}

struct Square: Shape {
    let size: Double
    var name: String { "Square" }
    var area: Double { size * size }
    static var sides: Int { 4 }
    init(size: Double) { self.size = size }
    func describe() -> String { "\(name) with area \(area)" }
}

final class Circle: Shape {
    let radius: Double
    var name: String { "Circle" }
    var area: Double { .pi * radius * radius }
    static var sides: Int { 0 }
    required init(size: Double) { radius = size }     // `required` in non-final classes
    func describe() -> String { "\(name) r=\(radius)" }
}

let shapes: [any Shape] = [Square(size: 2), Circle(size: 1)]
for s in shapes { print(s.describe()) }

19.2 Protocol Extensions and Default Implementations

Methods declared in the protocol are dynamically dispatched (customization points). Methods only in extensions are statically dispatched. A conformer's version won't be called through a protocol-typed value.

protocol Greetable {
    var name: String { get }
    func greet() -> String
}
extension Greetable {
    func greet() -> String { "Hello, \(name)!" }    // default implementation
    func shout() -> String { greet().uppercased() } // extra method for all conformers
}
struct Person: Greetable { let name: String }
Person(name: "Ana").shout()

19.3 Mutating Requirements, Class-Only Protocols and Composition

protocol Togglable { mutating func toggle() }
enum Switch: Togglable {
    case on, off
    mutating func toggle() { self = self == .on ? .off : .on }
}

protocol Delegate: AnyObject { func didFinish() }   // class-only (allows weak references)
class Worker { weak var delegate: Delegate? }

protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }
func wish(_ celebrator: some Named & Aged) {         // protocol composition
    print("Happy \(celebrator.age), \(celebrator.name)!")
}

19.4 Associated Types

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

struct Stack<Element>: Container {
    private var items: [Element] = []
    mutating func push(_ item: Element) { items.append(item) }
    mutating func pop() -> Element? { items.popLast() }
    mutating func append(_ item: Element) { push(item) }   // Item inferred as Element
    var count: Int { items.count }
    subscript(i: Int) -> Element { items[i] }
}

protocol Repository<Model> {                   // primary associated type (Swift 5.7)
    associatedtype Model
    func fetchAll() async throws -> [Model]
}
func load(from repo: some Repository<String>) async throws { _ = try await repo.fetchAll() }

19.5 Conditional Conformance and Self Requirements

extension Stack: Equatable where Element: Equatable {}   // conformance only when Element is Equatable

protocol Copyable { func copy() -> Self }
extension Array where Element: Numeric {
    var total: Element { reduce(0, +) }
}
[1, 2, 3].total

19.6 Protocol-Oriented Programming (POP)

Start with protocols, add behavior via extensions, prefer value types, and compose small protocols instead of deep class hierarchies.

Uses: dependency injection (protocol NetworkClient), testability (mock conformers), shared behavior across structs/enums/classes.