一、RxSwift 操作符分類概覽
| 類別 | 常見操作符 |
|---|---|
| 創(chuàng)建型 |
just, of, from, create, interval, timer
|
| 轉(zhuǎn)換型 |
map, flatMap, flatMapLatest, switchMap, concatMap
|
| 過濾型 |
filter, take, skip, distinctUntilChanged, debounce, throttle
|
| 組合型 |
merge, concat, combineLatest, zip, withLatestFrom
|
| 錯誤處理 |
catch, retry
|
| 工具型 |
do, subscribe(on:), observeOn, share, share(replay:1)
|
注意:RxSwift 中沒有
switchMap,它叫flatMapLatest;也沒有concatMap,用flatMap { $0 }+concat實現(xiàn)。
二、核心操作符詳解與對比
1. map vs flatMap vs flatMapLatest
? map
- 作用:一對一轉(zhuǎn)換。
- 輸入一個值 → 輸出一個新值(非 Observable)。
Observable.of(1, 2, 3)
.map { "\($0)!" }
.subscribe { print($0) }
// next("1!")
// next("2!")
// next("3!")
? flatMap
- 作用:將每個元素轉(zhuǎn)為一個 Observable,然后 合并所有內(nèi)部 Observable 的事件流(并發(fā))。
- 適用于多個異步任務(wù)同時進行。
let search = PublishSubject<String>()
search
.flatMap { query -> Observable<[String]> in
print("Searching for: $query)")
return simulateSearch(query) // 返回 Observable
}
.subscribe(onNext: { results in
print("Results: $results)")
})
?? 問題:如果用戶快速輸入 "a" → "ab" → "abc",三個搜索請求會同時發(fā)出并可能亂序返回("abc" 的結(jié)果可能先到,"a" 的后到)。
? flatMapLatest(等價于其他語言中的 switchMap)
- 作用:只保留最新的內(nèi)部 Observable,自動取消之前的。
- 非常適合搜索框、自動補全等場景。
search
.flatMapLatest { query in
print("Searching for: $query)")
return simulateSearch(query).delay(.milliseconds(500), scheduler: MainScheduler.instance)
}
.subscribe(onNext: { print("Got: $0)") })
? 效果:
- 輸入 "a" → 開始搜索
- 立即輸入 "ab" → 取消 "a" 的搜索,開始 "ab"
- 最終只顯示最新查詢的結(jié)果,避免競態(tài)條件。
?? 總結(jié)對比:
| 操作符 | 是否并發(fā) | 是否取消舊流 | 適用場景 |
|--------|--------|------------|--------|
|flatMap| ? 是 | ? 否 | 多個獨立異步任務(wù)(如批量上傳) |
|flatMapLatest| ? 否 | ? 是 | 搜索、實時預(yù)覽、最新請求優(yōu)先 |
|concatMap(需手動實現(xiàn)) | ? 否 | ? 否(排隊) | 嚴格順序執(zhí)行(如隊列任務(wù)) |
?? RxSwift 中沒有內(nèi)置
concatMap,但可通過flatMap { $0 }.concat()或使用enumerated().flatMap { index, value in ... }+ 隊列控制實現(xiàn)。
2. debounce vs throttle
兩者都用于限制高頻事件,但策略不同。
? debounce(防抖)
- 含義:在事件停止觸發(fā) 一段時間后 才發(fā)出最后一次值。
- 典型場景:搜索框輸入完成后再請求。
textField.rx.text.orEmpty
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.subscribe(onNext: { query in
print("Search after pause: $query)")
})
用戶輸入 "r", "rx", "rxs" —— 如果中間停頓超過 300ms,才觸發(fā)。
? throttle(節(jié)流)
- 含義:固定時間窗口內(nèi)最多發(fā)出一次(通常取窗口內(nèi)的第一個或最后一個值)。
- 默認是 leading: true, trailing: false(取第一個)。
button.rx.tap
.throttle(.seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { _ in
print("Button tapped (at most once per second)")
})
即使用戶狂點按鈕,每秒最多響應(yīng)一次。
?? 對比總結(jié):
| 特性 |debounce|throttle|
|------|-----------|-----------|
| 觸發(fā)時機 | 事件靜止后延遲觸發(fā) | 固定間隔觸發(fā) |
| 是否依賴“停止輸入” | ? 是 | ? 否 |
| 適合場景 | 搜索、保存草稿 | 按鈕防連點、滾動監(jiān)聽 |
?? 注意:
throttle在 RxSwift 6+ 中已更名為throttle(_:_:latest:),可通過latest: true改為取窗口最后一個值(類似 debounce 但有最大頻率限制)。
3. merge vs concat vs switchLatest(即 flatMapLatest)
假設(shè)我們有多個 Observable 流:
let stream1 = Observable.of("A1", "A2").delay(.seconds(1), scheduler: MainScheduler.instance)
let stream2 = Observable.of("B1", "B2").delay(.seconds(1), scheduler: MainScheduler.instance)
? merge
- 并發(fā)合并:誰先發(fā)出就先輸出。
Observable.of(stream1, stream2)
.merge()
// 可能輸出:A1, B1, A2, B2(順序不確定)
? concat
- 順序執(zhí)行:等前一個完成,再訂閱下一個。
Observable.of(stream1, stream2)
.concat()
// 輸出:A1, A2, B1, B2(嚴格順序)
? switchLatest(通過 flatMapLatest 實現(xiàn))
- 只保留最新流,丟棄舊的。
searchInput
.map { query in
return api.search(query) // 返回 Observable
}
.switchLatest() // 等價于 .flatMapLatest { $0 }
適用于“只關(guān)心最新查詢結(jié)果”的場景。
4. combineLatest vs zip
? combineLatest
- 只要任一源發(fā)出新值,就用各源的最新值組合發(fā)出。
- 不要求對齊,只要有初始值即可。
let a = BehaviorSubject(value: 1)
let b = BehaviorSubject(value: "X")
Observable.combineLatest(a, b) { ($0, $1) }
.subscribe { print($0) }
a.onNext(2) // 輸出 (2, "X")
b.onNext("Y") // 輸出 (2, "Y")
? zip
- 嚴格按順序配對:第1個和第1個配,第2個和第2個配……
- 必須雙方都有新值才發(fā)出。
let clicks = button.rx.tap.map { "click" }
let timer = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
Observable.zip(clicks, timer) { click, time in
return "\(click) at \(time)s"
}
.subscribe { print($0) }
只有當(dāng)點擊和計時器都至少發(fā)出一次,才會輸出。且第 n 次點擊對應(yīng)第 n 秒。
?? 對比:
| 特性 |combineLatest|zip|
|------|------------------|------|
| 觸發(fā)條件 | 任一源更新 | 所有源都更新(同步) |
| 數(shù)據(jù)對齊 | 用最新值 | 嚴格按發(fā)射順序 |
| 初始值要求 | 每個源至少有一個值 | 同上 |
| 典型用途 | 表單驗證、UI 狀態(tài)組合 | 動畫同步、步驟引導(dǎo) |
5. share() vs share(replay: 1)
用于將 冷 Observable 轉(zhuǎn)為熱 Observable,避免重復(fù)訂閱導(dǎo)致重復(fù)執(zhí)行。
let source = Observable.create { observer in
print("Executing network request...")
observer.onNext("Data")
observer.onCompleted()
return Disposables.create()
}
? 不加 share(冷 Observable)
source.subscribe { print("Sub1:", $0) }
source.subscribe { print("Sub2:", $0) }
// 輸出兩次 "Executing network request..."
? share()
- 多個訂閱者共享同一個執(zhí)行,但只轉(zhuǎn)發(fā)后續(xù)事件(不緩存歷史值)。
- 如果第一個訂閱者已收到 completed,第二個訂閱者將收不到任何值。
? share(replay: 1)
- 緩存最近 1 個值,新訂閱者立即收到該值。
- 非常適合狀態(tài)管理(如當(dāng)前用戶信息)。
let shared = source.share(replay: 1)
shared.subscribe { print("Sub1:", $0) } // 執(zhí)行請求,收到 Data
shared.subscribe { print("Sub2:", $0) } // 不執(zhí)行請求,直接收到 Data
?? 類似
BehaviorSubject的行為。
三、實戰(zhàn)建議
| 場景 | 推薦操作符 |
|---|---|
| 搜索框 |
debounce + flatMapLatest
|
| 表單驗證 |
combineLatest + map
|
| 按鈕防連點 | throttle |
| 多個獨立 API 請求 |
flatMap + merge
|
| 順序任務(wù)(如引導(dǎo)頁) | concat |
| 共享網(wǎng)絡(luò)請求結(jié)果 | share(replay: 1) |
| 錯誤重試 |
.retry(3) 或 .catch { fallback }
|
四、小結(jié):易混淆操作符速查表
| 對比組 | 關(guān)鍵區(qū)別 |
|---|---|
flatMap vs flatMapLatest
|
并發(fā) vs 取消舊流 |
debounce vs throttle
|
靜止后觸發(fā) vs 固定頻率 |
merge vs concat
|
并發(fā) vs 順序 |
combineLatest vs zip
|
最新值組合 vs 嚴格配對 |
share() vs share(replay:1)
|
無緩存 vs 緩存最近值 |