Swift 框架设计:Protocol Composition 与 Dependency Injection
Protocol Composition · Primary Associated Types · DI Container · Environment · Modularization
Swift 的协议组合(Protocol Composition)和依赖注入(DI)模式是构建大型 SwiftUI 应用的核心架构技能。Swift 6 引入的 Primary Associated Types 改进进一步增强了协议组合的表达力。本文从协议组合、Primary Associated Types 改进出发,解析依赖注入容器、Environment 键路径设计,以及在大型 SwiftUI 应用中的模块化经验。
一、协议组合:Swift 类型系统的复用基石
1.1 协议组合的基础
Swift 允许通过 & 运算符组合多个协议,形成一个新的协议类型。这种组合类型可以引用同时满足所有组合协议的类型:
// 协议组合示例
protocol Readable {
func read() async throws -> Data
}
protocol Writable {
func write(_ data: Data) async throws
}
// 组合协议:同时满足读写
typealias ReadWrite = Readable & Writable
// 使用组合协议作为参数类型
func syncData(_ storage: some ReadWrite) async throws {
let data = try await storage.read()
try await storage.write(data)
}
1.2 协议组合与泛型约束
协议组合可以用于泛型约束,限制泛型参数必须满足多个协议:
// 泛型函数中使用协议组合约束
func cache<T: Codable & Sendable>(_ value: T, _ key: String) {
// T 必须同时满足 Codable 和 Sendable
Cache.shared.store(value, forKey: key)
}
// Equatable & Hashable 组合:标准库常见模式
struct CacheKey: Hashable {
// Hashable 扩展了 Equatable
}
1.3 协议组合的限制
协议组合并非万能,存在以下限制:
- 组合的协议不能包含关联类型(除非使用
some或any)。 - 组合协议不能指定默认值。
- 过于复杂的协议组合可能导致类型推断失败。
二、Primary Associated Types:Swift 6 的协议增强
2.1 什么是 Primary Associated Types
Swift 6 引入了 Primary Associated Types 概念,它允许协作者(conforming)类型在遵循协议时指定具体类型参数,改善了泛型约束的表达力和编译器性能:
// Swift 6: 带 Primary Associated Types 的协议
protocol Container<typealias Element> {
associatedtype Element
var count: Int { get }
func element(_ index: Int) -> Element
}
// 遵循时指定 Primary Associated Type
struct IntArray: Container<Element = Int> {
var count: Int { 42 }
func element(_ index: Int) -> Int { index }
}
// 改进后的泛型约束查询
func process<C: Container>(_ c: C) where C.Element == Int {
// 编译器现在能更高效地解析 C.Element 类型
}
2.2 Primary Associated Types 的性能优势
Primary Associated Types 改进了泛型约束的解析速度:
- 更快的类型检查:编译器可以直接解析关联类型,无需泛型特化回溯。
- 更清晰的错误信息:类型不匹配错误更精确。
- 改进的代码补全:IDE 可以提供更准确的代码补全建议。
三、依赖注入容器:实践与模式
3.1 手动依赖注入
最基本的 DI 方式是手动注入依赖,这也是最容易理解和维护的方式:
// 手动依赖注入:通过初始化器注入
class UserRepository {
private let networkClient: NetworkClient
private let cache: CacheStore
init(networkClient: NetworkClient, cache: CacheStore) {
self.networkClient = networkClient
self.cache = cache
}
func fetchUser(id: String) async throws -> User {
// 使用注入的依赖
}
}
// 调用点:显式构造依赖
let repository = UserRepository(
networkClient: NetworkClient(baseURL: URL(string: "https://api.example.com")!),
cache: CacheStore()
)
3.2 Protocol-Based DI 容器
对于大型应用,可以实现一个基于协议的 DI 容器:
// 简单 DI 容器实现
actor DIContainer {
private var services: [ObjectIdentifier: any Any] = [:]
private var factories: [ObjectIdentifier: () -> any Any] = [:]
func register<T: Any>(_ factory: @escaping () -> T) {
let id = ObjectIdentifier(T.self)
factories[id] = { factory() }
}
func resolve<T: Any>() -> T? {
let id = ObjectIdentifier(T.self)
return services[id] as? T
}
func build<T: Any>() -> T? {
let id = ObjectIdentifier(T.self)
if let existing = services[id] as? T {
return existing
}
if let factory = factories[id] {
let instance = factory()
services[id] = instance
return instance as? T
}
return nil
}
}
四、SwiftUI Environment:系统级依赖传递
4.1 Environment 的设计理念
SwiftUI 的 @Environment 提供了一种系统级的依赖传递机制,它允许在视图层级中隐式传递依赖,而无需显式初始化器注入:
// 定义 EnvironmentKey
struct APIClientKey: EnvironmentKey {
static let defaultValue: any APIClient = LiveAPIClient()
}
extension EnvironmentValues {
var apiClient: any APIClient {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}
// 使用 @Environment
struct ProfileView: View {
@Environment(\.apiClient) var apiClient
var body: some View {
Text("Profile")
.task {
let profile = try await apiClient.fetchProfile()
}
}
}
4.2 Environment 的键路径设计
良好的 Environment 键路径设计应该遵循以下原则:
- 单一职责:每个 EnvironmentKey 只包含一个依赖。
- 协议导向:使用协议而非具体类型作为值类型。
- 默认值合理:每个键提供有意义的默认值。
.environment(\.apiClient, MockAPIClient()) 轻松替换依赖实现。这是 SwiftUI 架构的核心优势之一。
五、大型 SwiftUI 应用的模块化经验
5.1 模块划分原则
大型 SwiftUI 应用应该按照以下维度划分模块:
按功能领域划分
每个业务领域(用户、订单、支付)独立模块,有清晰的公共 API 边界。
按层次划分
UI 层、Domain 层、Data 层分离,每层有明确的依赖方向。
5.2 模块间依赖管理
// 模块化架构示例
// UserModule (独立模块)
public protocol UserRepositoryProtocol: Sendable {
func fetchUser(id: String) async throws -> User
}
// OrderModule (依赖 UserModule)
// 只依赖 UserRepositoryProtocol,不依赖具体实现
class OrderService {
init(userRepository: any UserRepositoryProtocol) {
self.userRepository = userRepository
}
}