在 RxSwift 中,Subject 是一種特殊類型,它 同時扮演 Observable(可被訂閱)和 Observer(可接收事件) 的角色,常用于橋接非響應(yīng)式代碼、狀態(tài)管理或事件廣播。
RxSwift 提供了 4 種內(nèi)置 Subject,RxCocoa 補(bǔ)充了 2 種更安全的 Relay(可視為“Subject 的安全封裝”)。
下面將 系統(tǒng)整理所有 6 種類型,包括:
- 定義
- 行為特點(diǎn)
- 使用場景
- 完整代碼示例
- 對比總結(jié)表
? 一、四大 Subject(來自 RxSwift)
1. PublishSubject<Element>
?? 行為:
- 只向訂閱之后的觀察者廣播事件
- 不緩存任何歷史值
- 可手動調(diào)用
.onError()或.onCompleted()終止流
?? 適用場景:
- 純事件廣播(如按鈕點(diǎn)擊、通知)
- 不關(guān)心歷史,只處理未來事件
?? 示例:
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>
?? 行為:
- 必須提供初始值
- 緩存最近一個值
- 新訂閱者立即收到當(dāng)前值
- 可被
.onError()/.onCompleted()終止
?? 適用場景:
- 表示當(dāng)前狀態(tài)(如登錄狀態(tài)、開關(guān))
- 需要“總是有值”的語義
?? 示例:
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)
?? 風(fēng)險:若調(diào)用
onCompleted(),后續(xù)訂閱者收不到任何值!
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值 - 只有在調(diào)用
.onCompleted()后才發(fā)送值 - 如果出錯(
.onError),只傳遞錯誤
?? 適用場景:
- 異步任務(wù)的最終結(jié)果(如文件下載路徑、初始化完成)
- 只關(guān)心“最后一刻”的值
?? 示例:
let subject = AsyncSubject<String>()
subject.subscribe { print("Sub1:", $0) }
subject.onNext("A")
subject.onNext("B")
subject.onNext("C")
subject.onCompleted() // 觸發(fā)發(fā)送
輸出:
Sub1: next(C)
Sub1: completed
? 若 never completed,訂閱者永遠(yuǎn)收不到值!
? 二、兩大 Relay(來自 RxCocoa,更安全)
Relay 是 永不失敗、不能完成 的 Subject 封裝,適合 UI 和狀態(tài)管理。
5. BehaviorRelay<Element>
?? 行為:
- 必須提供初始值
- 新訂閱者立即收到當(dāng)前值
- 只能通過
.accept(newValue)更新 - 永不發(fā)出
.error或.completed - 內(nèi)部基于
PublishSubject封裝
?? 適用場景:
- UI 狀態(tài)(用戶名、加載中、開關(guān))
- 替代
BehaviorSubject(更安全)
?? 示例:
let relay = BehaviorRelay(value: false)
relay.subscribe(onNext: { print("isLoggedIn:", $0) }) // 立即打印 false
relay.accept(true) // 更新值
輸出:
isLoggedIn: false
isLoggedIn: true
? 優(yōu)勢:無法被意外完成,適合長期存在的狀態(tài)。
6. PublishRelay<Element>
?? 行為:
- 不緩存歷史值
- 新訂閱者只收新事件
- 只能
.accept(_:)發(fā)送 - 永不失敗、不能完成
?? 適用場景:
- 事件指令(如“顯示彈窗”、“跳轉(zhuǎn)頁面”)
- 替代
PublishSubject(更安全)
?? 示例:
let relay = PublishRelay<Void>()
relay.subscribe(onNext: { print("Event triggered!") })
relay.accept(()) // 觸發(fā)事件
輸出:
Event triggered!
? 優(yōu)勢:不會因
onCompleted()導(dǎo)致后續(xù)事件丟失。
? 三、完整對比總結(jié)表
| 類型 | 初始值 | 重放歷史 | 可 error/complete | 永不終止 | 典型用途 |
|---|---|---|---|---|---|
PublishSubject |
? | ? | ? | ? | 事件廣播(原始) |
BehaviorSubject |
? | 最近 1 個 | ? | ? | 當(dāng)前狀態(tài)(有風(fēng)險) |
ReplaySubject |
? | 最近 N 個 | ? | ? | 歷史回放 |
AsyncSubject |
? | 僅最后一個(需 complete) | ? | ? | 異步最終結(jié)果 |
BehaviorRelay |
? | 最近 1 個 | ? | ? | UI 狀態(tài)(推薦) |
PublishRelay |
? | ? | ? | ? | 事件指令(推薦) |
? 四、如何選擇?—— 決策指南
| 你的需求 | 推薦類型 |
|---|---|
| “我想表示一個當(dāng)前狀態(tài),新訂閱者要知道現(xiàn)在是什么” | ? BehaviorRelay
|
| “我想廣播一個瞬時事件,如點(diǎn)擊、通知” | ? PublishRelay
|
| “我需要讓新訂閱者看到最近 3 條聊天記錄” | ReplaySubject(bufferSize: 3) |
| “我只關(guān)心異步操作的最終結(jié)果” | AsyncSubject |
| “我需要手動控制流的完成或錯誤(極少情況)” |
BehaviorSubject / PublishSubject
|
? 五、最佳實(shí)踐建議
-
優(yōu)先使用 Relay 而非 Subject
-
BehaviorRelay代替BehaviorSubject -
PublishRelay代替PublishSubject
-
-
對外隱藏寫入能力
private let _isLoading = BehaviorRelay(value: false) var isLoading: Observable<Bool> { _isLoading.asObservable() } 避免在 ViewModel 中暴露 Subject
Relay 更安全,防止外部意外終止流。不要濫用大 bufferSize 的 ReplaySubject
可能導(dǎo)致內(nèi)存問題。
? 六、一句話記憶口訣
- Publish:后來者,從現(xiàn)在開始聽
- Behavior:當(dāng)前狀態(tài),隨時可查
- Replay:回放歷史,N 條為限
- Async:干完活,只告訴你最后一句
- Relay:安全版,永不崩潰不終結(jié)
通過合理選擇 Subject/Relay,你可以構(gòu)建出語義清晰、健壯、易維護(hù)的響應(yīng)式系統(tǒng)。在現(xiàn)代 RxSwift 開發(fā)中,BehaviorRelay 和 PublishRelay 應(yīng)作為默認(rèn)選擇,僅在特殊需求下才使用原始 Subject。