Swift Protocols

Protocols define contracts that types must conform to. Swift's error handling uses typed errors with try/catch.

Protocols

SWIFT
// Protocol definition
protocol Drawable {
    func draw()
    
    // Default implementation
    func highlight() {
        print("Highlighted!")
    }
}

// Conformance
struct Circle: Drawable {
    let radius: Double
    
    func draw() {
        print("Drawing circle with radius \(radius)")
    }
    // highlight() uses default implementation
}

struct Square: Drawable {
    let side: Double
    
    func draw() {
        print("Drawing square with side \(side)")
    }
}

// Protocol with associated types
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(index: Int) -> Item { get }
}

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

Protocol Extensions

SWIFT
// Add functionality to all conforming types
protocol Greetable {
    var name: String { get }
}

extension Greetable {
    func greet() {
        print("Hello, I'm \(name)!")
    }
    
    func formalGreeting() {
        print("Good day. My name is \(name).")
    }
}

struct User: Greetable {
    let name: String
}

User(name: "Alice").greet()  // Hello, I'm Alice!

// Protocol composition
func display(_ item: Drawable & Greetable) {
    item.draw()
    item.greet()
}

// Protocol in extensions
extension Array: Container where Element: Codable {
    // Custom conformance
}

Error Handling

SWIFT
// Define error types
enum NetworkError: Error {
    case timeout
    case invalidResponse(statusCode: Int)
    case unauthorized
    case decodingFailed(underlying: Error)
}

// Functions that can throw
func fetchData(from url: String) throws -> Data {
    guard let url = URL(string: url) else {
        throw NetworkError.invalidResponse(statusCode: 0)
    }
    
    // In real code, this would be async
    guard let data = try? Data(contentsOf: url) else {
        throw NetworkError.timeout
    }
    
    return data
}

// Handling errors
do {
    let data = try fetchData(from: "https://api.example.com")
    print("Got \(data.count) bytes")
} catch NetworkError.timeout {
    print("Request timed out")
} catch NetworkError.invalidResponse(let code) {
    print("Invalid response: \(code)")
} catch {
    print("Unexpected error: \(error)")
}

// try? — Convert to optional
let data = try? fetchData(from: "https://api.example.com")
// data is Data?

// try! — Force (crash on error)
// let data = try! fetchData(from: "https://api.example.com")  // Dangerous!

// Propagate errors
func processUser() throws {
    let data = try fetchData(from: "/users")
    // ... process data
}

// Rethrows
func perform(_ operation: () throws -> Void) rethrows {
    try operation()
}

Enums with Associated Values

SWIFT
// Powerful pattern for modeling states
enum LoadingState<T> {
    case idle
    case loading(progress: Double)
    case success(data: T)
    case failure(error: Error)
    
    var isLoading: Bool {
        if case .loading = self { return true }
        return false
    }
}

var state: LoadingState<[User]> = .loading(progress: 0.5)

switch state {
case .idle:
    print("Ready")
case .loading(let progress):
    print("Loading: \(Int(progress * 100))%")
case .success(let users):
    print("Got \(users.count) users")
case .failure(let error):
    print("Error: \(error)")
}

💡 Tip: Use protocols for defining capabilities, not just types. Protocol-oriented design makes your code more testable and flexible. Define protocols first, then build concrete types.

Next: Swift Collections