在 RxSwift 中,Subject 是一种特殊类型,它 同时扮演 Observable(可被订阅)和 Observer(可接收事件) 的角色,常用于桥接非响应式代码、状态管理或事件广播。
RxSwift 提供了 4 种内置 Subject,RxCocoa 补充了 2 种更安全的 Relay(可视为“Subject 的安全封装”)。
下面将 系统整理所有 6 种类型,包括:
- 定义
- 行为特点
- 使用场景
- 完整代码示例
- 对比总结表
✅ 一、四大 Subject(来自 RxSwift)
1. PublishSubject<Element>
🔹 行为:
- 只向订阅之后的观察者广播事件
- 不缓存任何历史值
- 可手动调用
.onError()或.onCompleted()终止流
📌 适用场景:
- 纯事件广播(如按钮点击、通知)
- 不关心历史,只处理未来事件
💡 示例:
let subject = PublishSubject<String>()
subject.onNext("A") // 无订阅者,丢弃
subject.subscribe { print("Sub1:", $0) }
subject.onNext("B") // Sub1 收到 B
subject.subscribe { print("Sub2:", $0) }
subject.onNext("C") // Sub1 和 Sub2 都收到 C
输出:
Sub1: next(B)
Sub1: next(C)
Sub2: next(C)
2. BehaviorSubject<Element>
🔹 行为:
- 必须提供初始值
- 缓存最近一个值
- 新订阅者立即收到当前值
- 可被
.onError()/.onCompleted()终止
📌 适用场景:
- 表示当前状态(如登录状态、开关)
- 需要“总是有值”的语义
💡 示例:
let subject = BehaviorSubject(value: "INIT")
subject.subscribe { print("Sub1:", $0) } // 立即收到 INIT
subject.onNext("X")
subject.subscribe { print("Sub2:", $0) } // 立即收到 X
输出:
Sub1: next(INIT)
Sub1: next(X)
Sub2: next(X)
⚠️ 风险:若调用
onCompleted(),后续订阅者收不到任何值!
3. ReplaySubject<Element>
🔹 行为:
- 缓存最近 N 个事件(FIFO)
- 新订阅者收到全部缓存事件
- 可配置
bufferSize - 可被终止
📌 适用场景:
- 聊天消息(显示最近几条)
- 日志回放
- 需要“重放历史”的场景
💡 示例:
let subject = ReplaySubject<String>.create(bufferSize: 2)
subject.onNext("1")
subject.onNext("2")
subject.onNext("3") // 缓存 ["2", "3"]
subject.subscribe { print("Sub1:", $0) } // 收到 2, 3
subject.onNext("4") // 缓存 ["3", "4"]
subject.subscribe { print("Sub2:", $0) } // 收到 3, 4
输出:
Sub1: next(2)
Sub1: next(3)
Sub1: next(4)
Sub2: next(3)
Sub2: next(4)
4. AsyncSubject<Element>
🔹 行为:
- 只记住
.completed前的最后一个.next值 - 只有在调用
.onCompleted()后才发送值 - 如果出错(
.onError),只传递错误
📌 适用场景:
- 异步任务的最终结果(如文件下载路径、初始化完成)
- 只关心“最后一刻”的值
💡 示例:
let subject = AsyncSubject<String>()
subject.subscribe { print("Sub1:", $0) }
subject.onNext("A")
subject.onNext("B")
subject.onNext("C")
subject.onCompleted() // 触发发送
输出:
Sub1: next(C)
Sub1: completed
❗ 若 never completed,订阅者永远收不到值!
✅ 二、两大 Relay(来自 RxCocoa,更安全)
Relay 是 永不失败、不能完成 的 Subject 封装,适合 UI 和状态管理。
5. BehaviorRelay<Element>
🔹 行为:
- 必须提供初始值
- 新订阅者立即收到当前值
- 只能通过
.accept(newValue)更新 - 永不发出
.error或.completed - 内部基于
PublishSubject封装
📌 适用场景:
- UI 状态(用户名、加载中、开关)
- 替代
BehaviorSubject(更安全)
💡 示例:
let relay = BehaviorRelay(value: false)
relay.subscribe(onNext: { print("isLoggedIn:", $0) }) // 立即打印 false
relay.accept(true) // 更新值
输出:
isLoggedIn: false
isLoggedIn: true
✅ 优势:无法被意外完成,适合长期存在的状态。
6. PublishRelay<Element>
🔹 行为:
- 不缓存历史值
- 新订阅者只收新事件
- 只能
.accept(_:)发送 - 永不失败、不能完成
📌 适用场景:
- 事件指令(如“显示弹窗”、“跳转页面”)
- 替代
PublishSubject(更安全)
💡 示例:
let relay = PublishRelay<Void>()
relay.subscribe(onNext: { print("Event triggered!") })
relay.accept(()) // 触发事件
输出:
Event triggered!
✅ 优势:不会因
onCompleted()导致后续事件丢失。
✅ 三、完整对比总结表
| 类型 | 初始值 | 重放历史 | 可 error/complete | 永不终止 | 典型用途 |
|---|---|---|---|---|---|
PublishSubject |
❌ | ❌ | ✅ | ❌ | 事件广播(原始) |
BehaviorSubject |
✅ | 最近 1 个 | ✅ | ❌ | 当前状态(有风险) |
ReplaySubject |
❌ | 最近 N 个 | ✅ | ❌ | 历史回放 |
AsyncSubject |
❌ | 仅最后一个(需 complete) | ✅ | ❌ | 异步最终结果 |
BehaviorRelay |
✅ | 最近 1 个 | ❌ | ✅ | UI 状态(推荐) |
PublishRelay |
❌ | ❌ | ❌ | ✅ | 事件指令(推荐) |
✅ 四、如何选择?—— 决策指南
| 你的需求 | 推荐类型 |
|---|---|
| “我想表示一个当前状态,新订阅者要知道现在是什么” | ✅ BehaviorRelay
|
| “我想广播一个瞬时事件,如点击、通知” | ✅ PublishRelay
|
| “我需要让新订阅者看到最近 3 条聊天记录” | ReplaySubject(bufferSize: 3) |
| “我只关心异步操作的最终结果” | AsyncSubject |
| “我需要手动控制流的完成或错误(极少情况)” |
BehaviorSubject / PublishSubject
|
✅ 五、最佳实践建议
-
优先使用 Relay 而非 Subject
-
BehaviorRelay代替BehaviorSubject -
PublishRelay代替PublishSubject
-
-
对外隐藏写入能力
private let _isLoading = BehaviorRelay(value: false) var isLoading: Observable<Bool> { _isLoading.asObservable() } 避免在 ViewModel 中暴露 Subject
Relay 更安全,防止外部意外终止流。不要滥用大 bufferSize 的 ReplaySubject
可能导致内存问题。
✅ 六、一句话记忆口诀
- Publish:后来者,从现在开始听
- Behavior:当前状态,随时可查
- Replay:回放历史,N 条为限
- Async:干完活,只告诉你最后一句
- Relay:安全版,永不崩溃不终结
通过合理选择 Subject/Relay,你可以构建出语义清晰、健壮、易维护的响应式系统。在现代 RxSwift 开发中,BehaviorRelay 和 PublishRelay 应作为默认选择,仅在特殊需求下才使用原始 Subject。