# Protocol-Oriented Programming ## Protocol Basics ```swift // Protocol with requirements protocol Drawable { var boundingBox: CGRect { get } func draw(in context: CGContext) } // Protocol with default implementation extension Drawable { func draw(in context: CGContext) { // Default drawing behavior context.stroke(boundingBox) } } // Struct conforming to protocol struct Circle: Drawable { let center: CGPoint let radius: CGFloat var boundingBox: CGRect { CGRect( x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2 ) } } ``` ## Associated Types ```swift // Protocol with associated type protocol Container { associatedtype Item var count: Int { get } mutating func append(_ item: Item) subscript(index: Int) -> Item { get } } // Generic struct conforming struct Stack: Container { typealias Item = Element // Can be inferred private var items: [Element] = [] var count: Int { items.count } mutating func append(_ item: Element) { items.append(item) } subscript(index: Int) -> Element { items[index] } } // Using where clause with associated types extension Container where Item: Equatable { func firstIndex(of item: Item) -> Int? { for (index, current) in enumerated() where current == item { return index } return nil } } ``` ## Protocol Composition ```swift // Multiple protocol conformance protocol Named { var name: String { get } } protocol Aged { var age: Int { get } } // Composing protocols typealias Person = Named & Aged func greet(_ person: some Named & Aged) { print("Hello \(person.name), age \(person.age)") } // Protocol composition in constraints func process(_ items: [T]) { // T must conform to both Codable and Hashable } ``` ## Generics with Protocols ```swift // Generic function with protocol constraint func compare(_ a: T, _ b: T) -> T { return a > b ? a : b } // Generic type with protocol constraint class Repository { private var items: [Model.ID: Model] = [:] func save(_ model: Model) { items[model.id] = model } func find(id: Model.ID) -> Model? { items[id] } func all() -> [Model] { Array(items.values) } } // Using opaque return types func makeCollection() -> some Collection { return [1, 2, 3, 4, 5] } // Primary associated types (Swift 5.7+) protocol DataSource { associatedtype Element func fetch() async throws -> [Element] } func loadData(from source: some DataSource) async throws -> [T] { try await source.fetch() } ``` ## Type Erasure ```swift // Problem: Can't use protocol with associated types as type // protocol Storage { // associatedtype Item // func store(_ item: Item) // } // var storage: Storage // Error: protocol can only be used as constraint // Solution: Type-erased wrapper protocol Storage { associatedtype Item func store(_ item: Item) func retrieve() -> Item? } struct AnyStorage: Storage { typealias Item = T private let _store: (T) -> Void private let _retrieve: () -> T? init(_ storage: S) where S.Item == T { _store = storage.store _retrieve = storage.retrieve } func store(_ item: T) { _store(item) } func retrieve() -> T? { _retrieve() } } // Now we can use it as a type class MemoryStorage: Storage { private var item: T? func store(_ item: T) { self.item = item } func retrieve() -> T? { item } } let storage: AnyStorage = AnyStorage(MemoryStorage()) ``` ## Protocol Inheritance ```swift // Protocol inheriting from another protocol Identifiable { var id: UUID { get } } protocol Timestampable { var createdAt: Date { get } var updatedAt: Date { get } } protocol Entity: Identifiable, Timestampable { var version: Int { get } } struct User: Entity { let id: UUID let createdAt: Date var updatedAt: Date var version: Int var name: String } ``` ## Conditional Conformance ```swift // Make Array conform to protocol when elements conform protocol Summarizable { var summary: String { get } } extension Array: Summarizable where Element: Summarizable { var summary: String { map { $0.summary }.joined(separator: ", ") } } struct Task: Summarizable { let title: String var summary: String { title } } let tasks = [Task(title: "Buy milk"), Task(title: "Walk dog")] print(tasks.summary) // "Buy milk, Walk dog" ``` ## Protocol Extensions ```swift // Adding functionality to all conforming types protocol Collection { associatedtype Element var count: Int { get } subscript(index: Int) -> Element { get } } extension Collection { var isEmpty: Bool { count == 0 } func map(_ transform: (Element) -> T) -> [T] { var result: [T] = [] for i in 0.. Element { var total: Element = 0 for i in 0.. { let value: Double init(_ value: Double) { self.value = value } } extension Temperature where Unit == Celsius { func toFahrenheit() -> Temperature { Temperature(value * 9/5 + 32) } } extension Temperature where Unit == Fahrenheit { func toCelsius() -> Temperature { Temperature((value - 32) * 5/9) } } let celsius = Temperature(100) let fahrenheit = celsius.toFahrenheit() // Witness tables pattern protocol Encoder { func encode(_ value: T) throws -> Data } protocol Decoder { func decode(_ type: T.Type, from data: Data) throws -> T } struct Codec { let encoder: E let decoder: D func roundtrip(_ value: T) throws -> T { let data = try encoder.encode(value) return try decoder.decode(T.self, from: data) } } ``` ## Retroactive Modeling ```swift // Adding protocol conformance to types you don't own extension Int: Identifiable { public var id: Int { self } } // Now Int can be used where Identifiable is required let numbers: [Int] = [1, 2, 3] ForEach(numbers) { number in Text("\(number)") } ``` ## Best Practices - Prefer protocols over base classes for abstraction - Use protocol extensions for default implementations - Design protocols with single responsibility - Use associated types for generic protocols - Apply type erasure when needed for storage - Leverage conditional conformance - Use opaque return types (some Protocol) for implementation hiding - Compose small protocols rather than large ones - Document protocol requirements and guarantees - Consider protocol inheritance for layered abstraction