软件设计

何时引入 DI 容器?Swinject 与 Factory 采用时机总结

进行 iOS 开发时,总有一天会出现这样的问题。

4 分钟阅读
何时引入 DI 容器?Swinject 与 Factory 采用时机总结 封面图

进行 iOS 开发时,总有一天会出现这样的问题。

“这个直接通过构造器传入就行,真的有必要连 DI 容器也用上吗?”

随着 side project 变大,每个人都会遇到一次这样的烦恼。我在实际项目中都使用过 Swinject 和 Factory,并据此建立了判断标准。

先说结论。

如果页面少于 20 个,只用构造器注入、不使用 DI 容器就足够了。

当依赖关系图变复杂、测试替身需要频繁替换时,就是引入的时机。

如果要引入,选择 Factory。

我会按照自己的经历,说明什么时候引入,以及为什么选择 Factory。

引入 DI 容器前,请先确认这些事项

先整理最容易混淆的部分。

依赖注入(DI)和 DI 容器并不是一回事。

依赖注入是从外部提供所需对象的设计习惯,完全不需要库。

// 这也已经是很好的依赖注入
final class LoginViewModel {
    private let authService: AuthService
    init(authService: AuthService) {
        self.authService = authService
    }
}

而 DI 容器则是在一个地方统一管理这些对象在哪里、如何创建的工具。

如果只靠构造器注入就够了,现在引入容器还太早。

我用下面这些信号判断是否该引入。

  1. 初始化代码在每个页面重复,开始觉得传递对象很麻烦时
  2. 测试中经常需要替换成伪对象时
  3. 应用全局需要让多个地方共享同一个实例(具有单例性质)时
  4. 团队变大,经常问“这个对象在哪里创建?”时

如果其中两项以上符合,就可以开始考虑引入容器。


Swinject 和 Factory 有什么区别?

Swinject 是 Swift 生态中自 2015 年起使用的传统运行时容器。它注册到 Container,再通过 resolve 取出。问题在于,resolve 会返回可选值。忘记注册时,编译仍能正常通过,直到运行应用并在该页面强制解包 nil 时才会崩溃。

Factory 正是为弥补这类传统容器的缺点而诞生的项目。创建 Resolver 的 Michael Long 结合这段经验重新设计了它。核心理念是“定义即注册”。由于工厂定义为容器的属性,根本不存在漏注册的问题。错误引用会在编译时而不是运行时产生错误。

下面整理了截至 2026 年我感受到的差异。

项目 Swinject Factory
注册与解析方式 运行时 resolve(返回可选值) 基于类型的定义 + 属性包装器
漏注册时 运行时 nil/崩溃 通过编译错误提前拦截
作用域管理 支持 支持(.singleton.cached.shared 等)
外部依赖 依赖独立框架 轻量单一包
学习曲线 略陡 比较平缓

过去有人建议“需要复杂作用域管理时就用 Swinject”,但现在已经不是这样了。Factory 同样完整支持单例、缓存和共享作用域。Swinject 能做的事,Factory 可以更安全地完成。

实际接入后发现,Factory 一个文件就能开始
实际接入后发现,Factory 一个文件就能开始

所以结论是 Factory

我实际判断时采用的标准如下。

如果现在引入,Factory 一个就够了

无论是个人项目还是团队项目,Factory 都能在没有外部依赖的情况下从一个包开始,因此负担很小。最关键的是,因忘记注册而在运行时崩溃的事故从结构上消失了。

// Factory: 定义即注册
extension Container {
    var authService: Factory<AuthService> {
        self { LiveAuthService() }.singleton  // 作用域也只需一行
    }
}
// 使用方
@Injected(\.authService) private var authService

在测试中替换成 mock 也只需一行。

Container.shared.authService.register { MockAuthService() }

什么时候保留 Swinject?

维护已经使用 Swinject 构建的大型代码库时。不必立即移除运行良好的 assembly 结构。不过,即使在这类项目中,也有越来越多团队从新模块开始逐步迁移到 Factory。新项目采用时,现在很难找到选择 Swinject 的理由。


常见问题(Q&A)

Q. 不能从一开始就使用容器吗?

不反对,但不推荐。依赖关系图简单时,容器反而会多隐藏一层代码,让追踪变得困难。

Q. 可以从 Swinject 迁移到 Factory 吗?

可以。让两个容器暂时共存,再按模块迁移,是比较现实的渐进式迁移方式。由于需要重新调整注册位置和作用域设置,大型项目会花些时间,但迁移后就不用担心运行时崩溃了。

Q. SwiftUI 项目选哪个更好?

当然是 Factory。它基于属性包装器,能自然融入 SwiftUI 的声明式风格,在预览中插入 mock 也很简单。


DI 容器在真正需要时引入,最简洁。

既然决定引入,就直接选择 Factory。它为弥补现有容器的缺点而生,无论小规模开始还是逐步扩展都足够。先数一数前面提到的四个引入信号,现在符合几个。