Lesson 80Protocols

Protocols Practice

Module Summary

Congratulations on completing the Protocols module! You've learned how to design flexible, composable APIs using Protocol-Oriented Programming.

What You've Learned

Protocol Basics

Define blueprints for types

Requirements

Properties, methods, initializers

Inheritance

Compose protocol hierarchies

Associated Types

Generic protocols

Extensions

Add functionality to types

Protocol Extensions

Default implementations

Delegation

Communication pattern

Common Protocols

Equatable, Codable, etc.

Practice Exercises

1. Shape Drawing System

Combine Drawable, Colorable, Resizable protocols with default implementations.

2. API Service with Delegation

Implement delegate pattern for async API calls.

3. Codable Data Model

Create model with custom CodingKeys for JSON.

4. POP Game System

Compose game characters with Attackable, Defendable, Healable.

5. Generic Repository

Build reusable data storage with associated types.

Key Takeaways

  • Start with protocols - Design APIs around protocols first
  • Composition over inheritance - Use multiple protocols
  • Default implementations - Share code via extensions
  • Weak delegates - Avoid retain cycles
  • Value types + protocols - POP shines with structs
practice.swift
// PROTOCOLS PRACTICE
// Comprehensive exercises covering all protocol concepts

// ===== EXERCISE 1: Shape Drawing System =====
protocol Drawable {
    func draw()
}

protocol Colorable {
    var color: String { get set }
}

protocol Resizable {
    var scale: Double { get set }
    mutating func resize(by factor: Double)
}

// Default implementation
extension Resizable {
    mutating func resize(by factor: Double) {
        scale *= factor
    }
}

struct Rectangle: Drawable, Colorable, Resizable {
    var width: Double
    var height: Double
    var color: String
    var scale: Double = 1.0

    func draw() {
        let w = width * scale
        let h = height * scale
        print("Drawing \(color) rectangle: \(w)x\(h)")
    }
}

var rect = Rectangle(width: 10, height: 5, color: "blue")
rect.draw()
rect.resize(by: 2.0)
rect.draw()

// ===== EXERCISE 2: API Service with Delegation =====
protocol APIServiceDelegate: AnyObject {
    func apiDidStartLoading()
    func apiDidFinishLoading(data: [String: Any])
    func apiDidFailWithError(_ error: String)
}

class APIService {
    weak var delegate: APIServiceDelegate?

    func fetchData(endpoint: String) {
        delegate?.apiDidStartLoading()

        // Simulate API call
        let success = true
        if success {
            let data: [String: Any] = ["users": ["Alice", "Bob"]]
            delegate?.apiDidFinishLoading(data: data)
        } else {
            delegate?.apiDidFailWithError("Network error")
        }
    }
}

class DataManager: APIServiceDelegate {
    let api = APIService()

    init() {
        api.delegate = self
    }

    func loadUsers() {
        api.fetchData(endpoint: "/users")
    }

    func apiDidStartLoading() {
        print("Loading...")
    }

    func apiDidFinishLoading(data: [String: Any]) {
        print("Received: \(data)")
    }

    func apiDidFailWithError(_ error: String) {
        print("Error: \(error)")
    }
}

// ===== EXERCISE 3: Codable Data Model =====
struct User: Codable, Identifiable, Equatable {
    let id: Int
    var name: String
    var email: String
    var isActive: Bool

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case email
        case isActive = "is_active"
    }
}

// Encode
let user = User(id: 1, name: "Alice", email: "[email protected]", isActive: true)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
if let json = try? encoder.encode(user) {
    print(String(data: json, encoding: .utf8)!)
}

// ===== EXERCISE 4: Protocol-Oriented Game System =====
protocol Attackable {
    var attackPower: Int { get }
    func attack() -> Int
}

protocol Defendable {
    var defense: Int { get }
    func defend(damage: Int) -> Int
}

protocol Healable {
    var health: Int { get set }
    mutating func heal(amount: Int)
}

// Default implementations
extension Attackable {
    func attack() -> Int {
        return attackPower
    }
}

extension Defendable {
    func defend(damage: Int) -> Int {
        return max(0, damage - defense)
    }
}

extension Healable {
    mutating func heal(amount: Int) {
        health += amount
    }
}

// Compose character types
struct Warrior: Attackable, Defendable, Healable {
    var attackPower: Int = 20
    var defense: Int = 15
    var health: Int = 100
}

struct Mage: Attackable, Healable {
    var attackPower: Int = 30
    var health: Int = 60
}

struct Tank: Defendable, Healable {
    var defense: Int = 30
    var health: Int = 150
}

// ===== EXERCISE 5: Generic Repository =====
protocol Repository {
    associatedtype Entity: Identifiable
    func find(by id: Entity.ID) -> Entity?
    func save(_ entity: Entity)
    func delete(by id: Entity.ID)
    func all() -> [Entity]
}

struct InMemoryRepository<T: Identifiable>: Repository where T.ID: Hashable {
    private var storage: [T.ID: T] = [:]

    func find(by id: T.ID) -> T? {
        return storage[id]
    }

    mutating func save(_ entity: T) {
        storage[entity.id] = entity
    }

    mutating func delete(by id: T.ID) {
        storage.removeValue(forKey: id)
    }

    func all() -> [T] {
        return Array(storage.values)
    }
}

// Usage
struct Product: Identifiable {
    let id: Int
    var name: String
    var price: Double
}

var productRepo = InMemoryRepository<Product>()
productRepo.save(Product(id: 1, name: "iPhone", price: 999))
productRepo.save(Product(id: 2, name: "iPad", price: 799))

print(productRepo.all())

Module Complete!

You've mastered Swift Protocols! You now understand Protocol-Oriented Programming, the cornerstone of modern Swift development.

Next up: Error Handling - gracefully handle errors in your code!