bookworm-smart-assistant/skills/swift-expert/references/protocol-oriented.md

355 lines
7.5 KiB
Markdown

# 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<Element>: 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<T: Codable & Hashable>(_ items: [T]) {
// T must conform to both Codable and Hashable
}
```
## Generics with Protocols
```swift
// Generic function with protocol constraint
func compare<T: Comparable>(_ a: T, _ b: T) -> T {
return a > b ? a : b
}
// Generic type with protocol constraint
class Repository<Model: Codable & Identifiable> {
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<Element> {
associatedtype Element
func fetch() async throws -> [Element]
}
func loadData<T>(from source: some DataSource<T>) 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<T>: Storage {
typealias Item = T
private let _store: (T) -> Void
private let _retrieve: () -> T?
init<S: Storage>(_ 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<T>: Storage {
private var item: T?
func store(_ item: T) {
self.item = item
}
func retrieve() -> T? {
item
}
}
let storage: AnyStorage<String> = AnyStorage(MemoryStorage<String>())
```
## 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<T>(_ transform: (Element) -> T) -> [T] {
var result: [T] = []
for i in 0..<count {
result.append(transform(self[i]))
}
return result
}
}
// Constrained extensions
extension Collection where Element: Numeric {
func sum() -> Element {
var total: Element = 0
for i in 0..<count {
total += self[i]
}
return total
}
}
```
## Advanced Patterns
```swift
// Phantom types for type safety
enum Celsius {}
enum Fahrenheit {}
struct Temperature<Unit> {
let value: Double
init(_ value: Double) {
self.value = value
}
}
extension Temperature where Unit == Celsius {
func toFahrenheit() -> Temperature<Fahrenheit> {
Temperature<Fahrenheit>(value * 9/5 + 32)
}
}
extension Temperature where Unit == Fahrenheit {
func toCelsius() -> Temperature<Celsius> {
Temperature<Celsius>((value - 32) * 5/9)
}
}
let celsius = Temperature<Celsius>(100)
let fahrenheit = celsius.toFahrenheit()
// Witness tables pattern
protocol Encoder {
func encode<T: Encodable>(_ value: T) throws -> Data
}
protocol Decoder {
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T
}
struct Codec<E: Encoder, D: Decoder> {
let encoder: E
let decoder: D
func roundtrip<T: Codable>(_ 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