什么是依赖注入以及为什么在 Swift 中需要它
依赖注入(Dependency Injection,DI)是一种设计模式,对象从外部获取其依赖项,而不是自行创建它们。在 iOS 和 macOS 的 Swift 上下文中,DI 有助于使代码更灵活、可测试和可维护。您不是通过 init() 或 let 硬性绑定类,而是通过构造函数、属性或方法传递依赖项。
DI 的主要优点:
- 可测试性:您可以轻松地在单元测试中将真实对象替换为模拟对象或桩对象。
- 低耦合:类依赖于抽象(协议),而不是具体实现。
- 简化重构:代码某一部分的更改不会破坏另一部分。
- 清晰的架构:代码遵循单一职责原则和依赖反转原则(SOLID 中的 DIP)。
在 Swift 中,DI 在使用 UIKit、SwiftUI、CoreData、网络和其他服务时尤其重要。没有它,您可能会面临“上帝对象”和代码重用困难的风险。
Swift 中依赖注入的主要方式
1. 通过初始化器注入(Constructor Injection)
这是最常见且推荐的方式。依赖项在对象创建时传递,并变为不可变(let)。
protocol NetworkServiceProtocol { func fetchData() -> String}
class NetworkService: NetworkServiceProtocol { func fetchData() -> String { return "Данные с сервера" }}
class ViewModel { private let networkService: NetworkServiceProtocol
// 通过初始化器注入 init(networkService: NetworkServiceProtocol) { self.networkService = networkService }
func loadData() -> String { return networkService.fetchData() }}
// 使用let service = NetworkService()let viewModel = ViewModel(networkService: service)print(viewModel.loadData()) // "Данные с сервера"优点:依赖项明确,对象始终处于有效状态。缺点:当依赖项数量较多时,构造函数会变得冗长。
2. 通过属性注入(Property Injection)
依赖项在对象创建后设置。通常用于可选服务或使用 Storyboard/XIB 时。
class UserViewController: UIViewController { var analyticsService: AnalyticsServiceProtocol?
override func viewDidLoad() { super.viewDidLoad() analyticsService?.trackEvent("Экран открыт") }}
// 注入let vc = UserViewController()vc.analyticsService = AnalyticsService()缺点:属性必须是可选的或具有默认值,如果忘记设置依赖项,可能导致运行时错误。
3. 通过方法注入(Method Injection)
依赖项作为方法参数传递。适用于依赖项仅在某次操作中需要的情况。
class DataManager { func processData(using service: NetworkServiceProtocol) -> String { let rawData = service.fetchData() return "Обработано: \\(rawData)" }}
let manager = DataManager()let result = manager.processData(using: NetworkService())print(result) // "Обработано: Данные с сервера"在 iOS 应用中实现 DI 的示例
考虑一个实际示例:一个用于显示用户列表的应用。我们将创建带有依赖注入的协议和类。
// 1. 协议protocol UserRepositoryProtocol { func getUsers() -> [String]}
protocol UserViewModelProtocol { var users: [String] { get } func loadUsers()}
// 2. 仓库实现class UserRepository: UserRepositoryProtocol {