RxSwift 操作符

一、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 緩存最近值

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Subject創(chuàng)建訂閱Source PublishSubject 最普通的subject,對它訂閱的訂閱者只會收到...
    給傷的你我依然喜歡閱讀 1,218評論 0 1
  • 1.Amb 在多個源 Observables 中, 取第一個發(fā)出元素或產(chǎn)生事件的Observable ,然后只發(fā)出...
    前路星辰大海閱讀 1,054評論 0 0
  • 之前列舉了很多操作符的用法,還有很多我們沒有列舉的。其實寫了那么多操作符有時候我還是會忘記選擇哪一個。這個時候,我...
    darrenW閱讀 744評論 0 0
  • RxSwift操作符分類 一、我想要創(chuàng)建一個Observable 產(chǎn)生特定的一個元素:just 經(jīng)過一段延時:ti...
    joeal閱讀 688評論 0 1
  • just() 通過傳入默認值初始化,Observable<T>泛型,指定類型和不指定類型都無所謂,swift會自動...
    鄧布利多教授閱讀 1,206評論 0 1

友情鏈接更多精彩內(nèi)容