Architecture

Swift 框架设计:Protocol Composition 与 Dependency Injection

Protocol Composition · Primary Associated Types · DI Container · Environment · Modularization

📅 2026-05-26 👤 架构设计组 ⏱ 约18分钟

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 协议组合的限制

协议组合并非万能,存在以下限制:

设计建议:保持协议组合简洁(通常不超过 3-4 个协议)。过长的协议组合表明设计可能存在问题,考虑拆分或使用更抽象的协议。

二、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 改进了泛型约束的解析速度:

三、依赖注入容器:实践与模式

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 键路径设计应该遵循以下原则:

设计建议:对于测试场景,可以通过 .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
    }
}