使用 Swift 开发 iOS 应用时,总会遇到一个问题。
“依赖注入到底应该采用哪种方式?”
先说结论。
除非有特殊理由,否则使用 构造器(init)注入,通常是最接近正确答案的选择。
下面结合实际经验,说明原因以及另外两种方式适合在什么时候使用。
三种注入方式,先看核心要点
在感到困惑之前,先建立基本框架。Swift 中注入依赖主要有三种方式。
- 构造器(init)注入 —
init通过参数接收依赖 - 属性注入 —
var稍后赋值给属性 - 方法注入 — 通过单独的方法注入
先看最推荐的构造器注入代码。
final class OrderService {
private let repo: MemberRepository // let 保证不可变性
init(repo: MemberRepository) { // 构造器(init) 注入
self.repo = repo
}
}
它不需要额外库,只使用纯 Swift 语法,因此代码很简洁。
相比之下,属性注入可以写得这么短。
final class OrderService {
var repo: MemberRepository! // 只有一行,看起来很方便,但…
}
乍一看,属性注入确实更简单。但这种便利之后可能会带来问题。
为什么不建议使用属性注入?
先说说我亲身经历过的事情。
属性注入在测试时非常不方便。创建对象后如果忘记注入,就会因强制解包可选值而崩溃,而且只看类型也无法知道需要传入什么才能完成对象配置。
使用构造器注入时,可以像 OrderService(repo: mockRepo) 一样,直接通过纯 Swift 代码传入 Mock 对象。
第二个问题是无法使用 let 关键字。
构造器注入可以将属性声明为 let,注入一次后就成为不可变对象。
而属性注入使用 var,值可以随时改变,因此容易出错。
第三个问题是循环引用。
如果 A 引用 B,B 又引用 A,属性注入可能要等应用运行后才会发现,进入相关页面时才会崩溃。
构造器注入会在创建对象时立即暴露问题;如果设计混乱,甚至无法编译,因此能更早发现并修复问题。
一眼看懂的对比表
用文字说明容易混淆,所以整理成了表格。(截至 2026 年 Swift 社区推荐的方向)
| 分类 | 构造器(init)注入 | 属性注入 | 方法注入 |
|---|---|---|---|
| 不可变性(let) | 可以 ⭕ | 不可以 ❌ | 不可以 ❌ |
| 测试便利性 | 高 | 低 | 一般 |
| 循环引用检测 | 创建时立即检测 | 延迟发现 | 延迟发现 |
| 必需/可选依赖 | 适合必需依赖 | 难以区分 | 适合可选依赖 |
| 代码简洁度 | 一般 | 非常简洁 | 一般 |
只看表格也能看出整体趋势了吧?
构造器注入在大多数项目中都更胜一筹。因此,Swinject 和 Factory 等 DI 库的文档也都将构造器注入作为默认推荐方案。
那么另外两种方式应该什么时候使用?
这并不是说任何时候都只能使用构造器注入。每种方式都有适合自己的场景。
方法注入很适合可选依赖。
当依赖可以不存在,也就是有就使用、没有也没关系时,可以通过 configure(with:) 这类方法灵活地注入。
属性注入仍有适用场景,例如 Storyboard 创建的视图控制器,因为这类对象的初始化时机无法由我们直接控制。
在普通应用代码中,最好尽量避免使用它。
总结如下。
- 必需依赖 → 构造器注入
- 可选依赖 → 方法注入
- 普通代码中的属性注入 → 尽量避免
常见问题
Q. 使用 Factory 这样的 DI 库后,构造器注入会更方便吗?
会。在 Swinject 或 Factory 中注册依赖的创建方式后,容器会代替你创建传给构造器的对象。代码更短,同时还能保留 let 不可变性等优点。
Q. 如果构造器参数变得太多怎么办?
这不是注入方式的问题,而是该类承担了太多工作的信号。请先考虑拆分职责并分离类。
Q. 小型项目也必须使用 DI 库吗?
不需要。如果只有几个页面,仅使用 init 直接传入依赖的纯构造器注入就足够了。
一开始很容易被属性注入的便利性吸引,但在编写测试并进行协作后,你会亲身体会到为什么大家如此强调构造器注入的优点。
如果拿不准,就先从构造器注入开始。代码规模越大,你越会庆幸当初做了这个选择。

