随着 iOS 应用规模不断扩大,你可能也曾因依赖管理而头疼不已。
随着项目推进,你一定会遇到Swift Service Locator 模式。有人觉得它方便,也有人因为认为它是反模式而避之不及。
相比全面引入,我发现只在真正需要的地方局部使用,效果更好。
先说结论。
Service Locator 并不是 DI(Dependency Injection,依赖注入)的完整替代方案;使用不当时,它会成为隐藏依赖的反模式。它更像是一种辅助工具。
今天我会结合自己的经验,介绍这个模式是什么、为什么会受到批评,以及它在什么情况下仍然值得使用。
什么是 Service Locator 模式?
一句话来说,就是从中央注册表中取出所需对象来使用的方式。
在某处放置一个“注册表”,每次需要对象时都从那里查找并使用。
如果 Constructor Injection 是从外部注入依赖,那么 Service Locator 就是在内部直接取出依赖来使用。
结合代码,很快就能理解这个概念。
// 向中央注册表注册服务并取出使用的结构
final class ServiceLocator {
static let shared = ServiceLocator()
private var services: [String: Any] = [:]
func register<T>(_ service: T) { services["\(T.self)"] = service }
func resolve<T>() -> T { services["\(T.self)"] as! T }
}
在应用启动时注册一次即可。
实际使用的一方会这样取出所需依赖。
// 在视图模型内部直接“查找”依赖
final class FeedViewModel {
private let api: APIClient = ServiceLocator.shared.resolve()
// 即使不把 api放入构造器,也能正常工作
}
如你所见,最大的优点是构造器变得更简洁。
为什么 Service Locator 被称为反模式?
最大的原因只有一个:依赖被隐藏了。
再看一遍上面的FeedViewModel代码。只看构造器,根本无法知道这个类使用了APIClient。
必须打开内部实现才能看出来。在团队协作中,这会带来相当大的不便。
第二个问题是测试。
使用 Constructor Injection 时,测试只需直接传入 Mock。但 Service Locator 需要修改全局注册表状态,因此测试之间很容易相互影响。
第三个问题是运行时风险。
如果尝试取出忘记注册的依赖,应用不会在编译时发现问题,而是在运行过程中崩溃。上面示例中的强制类型转换as!正是问题所在。
总结如下。
| 比较项目 | Constructor Injection(DI) | Service Locator |
|---|---|---|
| 依赖暴露 | 清晰可见 | 隐藏在内部 |
| 测试便利性 | 高 | 较低 |
| 错误发现时机 | 编译时 | 运行时 |
| 构造器简洁度 | 参数增多 | 简洁 |
那么,什么时候可以放心使用呢?
它并非绝对糟糕。我曾在以下场景中觉得很实用。
- 整个应用共享的单一服务(日志记录器、分析工具等)
- Constructor Injection 路径过深,参数不断向下传递时
- 逐步将 DI 引入遗留代码的过渡期
尤其第三种情况很符合实际。
一次性把 Constructor Injection 加入现有代码的所有位置,负担很重。这时先用 Service Locator 搭一座临时的桥,再逐步迁移到 Constructor Injection,会更安全。
如今,相比纯粹的 Service Locator,更多人使用Swinject或 Swift 的@Environment等 DI 容器。
它们内部同样从注册表中取出对象,但注册验证和作用域管理更完善,因此能降低风险。
常见问题
问:它和 Singleton 有什么区别?
Singleton 让某个特定对象成为全局对象,而 Service Locator 则像是存放多个对象的“仓库”。两者的性质有所不同。
问:SwiftUI 中也会使用吗?
SwiftUI 的@Environment和@EnvironmentObject其实与 Service Locator 十分相似。可以说,Apple 在框架层面提供了类似的方式。
问:那么默认应该采用什么?
建议默认使用 Constructor Injection。Service Locator 只在确实需要的地方局部使用。
依赖管理没有唯一正确答案,但方向很明确。
尽可能让依赖显式可见,隐藏依赖的工具则只在必要的位置谨慎使用。只要做到这一点,维护就会轻松许多。

