Swift 中的依赖注入简介
依赖注入(Dependency Injection,简称 DI)是一种关键的设计原则,有助于在 Swift 中创建灵活、可测试且易于维护的应用程序。在 iOS 和 macOS 开发中,DI 将对象的创建与使用分离开来,从而显著简化了代码的测试和修改。
核心思想是:类不应自行创建其依赖项。相反,依赖项应从外部“注入”。这是 SOLID 原则中依赖倒置原则(Dependency Inversion Principle)的实现。
为什么依赖注入对 iOS/macOS 开发很重要?
在原生 Swift 应用程序中,经常遇到紧耦合(tight coupling)的问题。来看一个典型示例:
class UserService { let networkManager = NetworkManager() let database = Database() func fetchUser(id: Int) -> User { // 逻辑 }}这里 UserService 与 NetworkManager 和 Database 的具体实现紧密绑定。这会带来几个问题:
- 测试困难 — 无法用模拟对象(mocks)或桩(stubs)替换真实服务
- 可复用性低 — 该类无法与其他实现一起使用
- 违反单一职责原则(SRP) — 该类不仅处理业务逻辑,还负责创建依赖项
依赖注入通过允许通过构造函数、属性或方法注入依赖项来解决这些问题。
Swift 中实现依赖注入的方式
1. 构造函数注入(Constructor Injection)
最常用且推荐的方式。依赖项通过初始化器传递:
protocol NetworkService { func request(endpoint: String) -> Data}
protocol DatabaseService { func save(user: User)}
class UserService { private let networkService: NetworkService private let databaseService: DatabaseService init(networkService: NetworkService, databaseService: DatabaseService) { self.networkService = networkService self.databaseService = databaseService } func fetchUser(id: Int) -> User? { let data = networkService.request(endpoint: "users/\\(id)") // 解析数据 return User(id: id, name: "John") }}优点:依赖项明确可见,是类工作的必要条件,易于测试。
2. 属性注入(Property Injection)
依赖项在对象创建后设置:
class AuthManager { var networkService: NetworkService? func login(username: String, password: String) -> Bool { guard let service = networkService else { return false } // 身份验证逻辑 return true }}
// 使用let authManager = AuthManager()authManager.networkService = RealNetworkService()缺点:依赖项可能未设置,导致使用可选类型并可能引发崩溃。
3. 方法注入(Method Injection)
依赖项直接传递给方法:
class DataProcessor { func process(data: Data, using service: NetworkService) -> ProcessedData { let result = service.request(endpoint: "process") // 处理 return ProcessedData() }}适用于依赖项仅在某个方法中需要的情况。
实践示例:任务管理应用
我们来创建一个使用 DI 的简单 iOS 应用:
// 依赖项协议protocol TaskRepository { func fetchTasks() ->