Lesson 99Advanced Topics
Advanced Practice
Putting It All Together
These exercises combine multiple advanced concepts. Practice building real-world solutions using generics, actors, property wrappers, and more!
Practice Projects
1. Generic Cache with Actor
Thread-safe cache using generics and actors.
2. Async Network Layer
Protocol-based API client with async/await.
3. Property Wrapper + Codable
Custom wrapper for safe JSON decoding.
4. Result Builder
DSL for building arrays declaratively.
5. Generic Repository
Protocol with associated types for data access.
6. Type-Safe State Machine
Generics for compile-time state validation.
Concepts Combined
- Generics with constraints
- Actors for thread safety
- Async/await patterns
- Property wrappers
- Protocols with associated types
- Result builders
advanced_practice.swift
// ADVANCED PRACTICE
// Comprehensive exercises combining advanced concepts
// ===== EXERCISE 1: Generic Cache with Actor =====
actor Cache<Key: Hashable, Value> {
private var storage: [Key: Value] = [:]
private let maxSize: Int
init(maxSize: Int = 100) {
self.maxSize = maxSize
}
func get(_ key: Key) -> Value? {
return storage[key]
}
func set(_ key: Key, value: Value) {
if storage.count >= maxSize {
storage.removeAll() // Simple eviction
}
storage[key] = value
}
func clear() {
storage.removeAll()
}
}
// Usage
let imageCache = Cache<String, Data>(maxSize: 50)
Task {
await imageCache.set("avatar", value: Data())
let data = await imageCache.get("avatar")
}
// ===== EXERCISE 2: Async Network Layer =====
protocol APIClient {
func fetch<T: Decodable>(from url: URL) async throws -> T
}
actor NetworkManager: APIClient {
func fetch<T: Decodable>(from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
return try JSONDecoder().decode(T.self, from: data)
}
}
enum NetworkError: Error {
case invalidResponse
case decodingFailed
}
// ===== EXERCISE 3: Property Wrapper + Codable =====
@propertyWrapper
struct DefaultEmpty<T: RangeReplaceableCollection & Codable>: Codable {
var wrappedValue: T
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = (try? container.decode(T.self)) ?? T()
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
struct User: Codable {
var name: String
@DefaultEmpty var tags: [String] // Won't fail if missing
}
// ===== EXERCISE 4: Result Builder =====
@resultBuilder
struct ArrayBuilder<T> {
static func buildBlock(_ components: T...) -> [T] {
components
}
static func buildOptional(_ component: [T]?) -> [T] {
component ?? []
}
static func buildEither(first: [T]) -> [T] { first }
static func buildEither(second: [T]) -> [T] { second }
}
func buildArray<T>(@ArrayBuilder<T> _ content: () -> [T]) -> [T] {
content()
}
let numbers = buildArray {
1
2
3
}
print(numbers) // [1, 2, 3]
// ===== EXERCISE 5: Combine Generics + Protocols =====
protocol Repository {
associatedtype Entity: Identifiable
func fetch(id: Entity.ID) async throws -> Entity
func save(_ entity: Entity) async throws
func delete(id: Entity.ID) async throws
}
struct InMemoryRepository<T: Identifiable>: Repository {
typealias Entity = T
private var storage: [T.ID: T] = [:]
func fetch(id: T.ID) async throws -> T {
guard let entity = storage[id] else {
throw RepositoryError.notFound
}
return entity
}
mutating func save(_ entity: T) async throws {
storage[entity.id] = entity
}
mutating func delete(id: T.ID) async throws {
storage.removeValue(forKey: id)
}
}
enum RepositoryError: Error {
case notFound
case saveFailed
}
// ===== EXERCISE 6: Type-Safe State Machine =====
protocol State {}
protocol Event {}
struct StateMachine<S: State, E: Event> {
private(set) var currentState: S
private let transitions: (S, E) -> S?
init(initial: S, transitions: @escaping (S, E) -> S?) {
self.currentState = initial
self.transitions = transitions
}
mutating func send(_ event: E) -> Bool {
guard let newState = transitions(currentState, event) else {
return false
}
currentState = newState
return true
}
}
// Example usage
enum OrderState: State {
case pending, processing, shipped, delivered
}
enum OrderEvent: Event {
case pay, ship, deliver
}
var orderMachine = StateMachine<OrderState, OrderEvent>(
initial: .pending
) { state, event in
switch (state, event) {
case (.pending, .pay): return .processing
case (.processing, .ship): return .shipped
case (.shipped, .deliver): return .delivered
default: return nil
}
}
orderMachine.send(.pay) // Now .processing
orderMachine.send(.ship) // Now .shipped
orderMachine.send(.deliver) // Now .deliveredChallenge Yourself!
Combine multiple exercises: Create an async repository that uses the actor-based cache for performance!