Inject dependencies via protocols; assemble at the composition root.
protocol Analytics { func log(event: String) }
final class ConsoleAnalytics: Analytics { func log(event: String) { print("[A] \(event)") } }
struct Services { let analytics: Analytics; let repo: TodosRepo }
final class AppContainer {
let services: Services
init(baseURL: URL) {
let repo = HttpTodosRepo(baseURL: baseURL)
let analytics = ConsoleAnalytics()
self.services = Services(analytics: analytics, repo: repo)
}
}
Inject a repo via a container and build the SwiftUI tree.
// AppContainer.swift
protocol TodosRepo { func list() async throws -> [String] }
struct FakeTodosRepo: TodosRepo { func list() async throws -> [String] { ["A","B"] } }
struct Services { let repo: TodosRepo; let analytics: Analytics }
final class AppContainer { let services: Services; init() { self.services = .init(repo: FakeTodosRepo(), analytics: ConsoleAnalytics()) } }
// RootView.swift
struct RootView: View {
let services: Services
@State private var items: [String] = []
var body: some View {
List(items, id: \.self, rowContent: Text.init)
.task { items = (try? await services.repo.list()) ?? [] }
}
}
Notes
FakeTodosRepo
with a real implementation in production.Paste into an Xcode SwiftUI app (see sandboxes/ios-swiftui):
services
into views at the app entry