RxSwift 之過濾操作

BigNerdCoding發表於2019-01-23

Cover
Cover

在前面的基礎之上接下來我會介紹一些常用的函式和實用技巧。首先,本文將會介紹那些用於對 next 事件進行過濾的操作。這些過濾操作類似於 Swift 標準庫中的 filter 操作。它能在我們開始真正進行業務處理前先把那些不符合條件的過濾掉,而且這種函數語言程式設計的正規化也能開闊我們的思維。

Ignore 過濾

RxSwift 中最簡單直接的過濾操作就是 ignoreElements 了。該操作會遮蔽所有的 next 事件,只會將注意力放在 errorcompleted 事件上。如下圖所示,在整個生命週期中可觀察物件的所有 next 都被過濾。

01
01

示例程式碼:

let strikes = PublishSubject<String>()

let disposeBag = DisposeBag()

strikes
    .ignoreElements()
    .subscribe { _ in
        print("You're out!")
    }
    .addDisposableTo(disposeBag)

strikes.onNext("X")
strikes.onNext("X")
strikes.onNext("X")

strikes.onCompleted()

/* 列印結果
You're out!
*/複製程式碼

不過相比於殘暴的全部過濾,有時候我們可能只是需要過濾某些特定的事件。例如,我們可以通過 elementAt 對特定索引號 next 進行過濾。下圖演示了只響應第二個 next 事件的 elementAt 操作。

02
02

與之相應的程式碼為:

let strikes = PublishSubject<String>()

let disposeBag = DisposeBag()

strikes
    .elementAt(2)
    .subscribe(onNext: { str in
        print(str)
    })
    .addDisposableTo(disposeBag)

strikes.onNext("1")
strikes.onNext("2")
strikes.onNext("3")

strikes.onCompleted()

/* 列印結果
3
*/複製程式碼

上面兩個操作最後針對的 next 事件最多隻會有一個,但是大多數時候我們其實需要篩選出一組符合條件的 next 事件。下圖演示的就是使用 filter 篩選資料小於 3 的操作。

03
03

圖示對應程式碼如下:

let strikes = PublishSubject<Int>()

let disposeBag = DisposeBag()

strikes
    .filter{ $0 < 3 }
    .subscribe(onNext: { num in
        print("\(num)")
    })
    .addDisposableTo(disposeBag)

strikes.onNext(1)
strikes.onNext(2)
strikes.onNext(3)
strikes.onNext(4)
strikes.onNext(5)

strikes.onCompleted()

/* 列印結果
1
2
*/複製程式碼

Skip 過濾

除了忽略操作外,另一個常見的過濾就是跳過操作了。在所有的跳過操作中,最簡單的就屬 skip 了。通過設定引數,我們就能和簡單實現跳過指定個數的事件。例如,下圖久演示跳過前兩個事件的操作。

04
04

let strikes = PublishSubject<Int>()

let disposeBag = DisposeBag()

strikes
    .skip(2)
    .subscribe(onNext: { num in
        print("\(num)")
    })
    .addDisposableTo(disposeBag)

strikes.onNext(1)
strikes.onNext(2)
strikes.onNext(3)
strikes.onNext(4)
strikes.onNext(5)

strikes.onCompleted()

/* 列印結果
3
4
5
*/複製程式碼

當然除了跳過指定索引號的事件之外,我們依舊通過 skipWhile 我們能夠實現類似 filter 類似的操作。只不過 filter 會過濾整個生命週期內的符合條件的事件,而 skipWhile 在找到第一個不符合跳過操作的事件之後就不再工作。例如,下圖 skipWhile 的條件是資料為奇數就跳過,但是當資料 2 執行之後 資料 3 雖然也是奇數但是不會在跳過。所以嚴格意義上來說 skipWhile 可能有點歧義,實際是它會跳過所有符合條件的事件,直到找到第一個能執行事件後就不再生效。

05
05

下面是跳過偶數的 skipWhile 程式碼:

let strikes = PublishSubject<Int>()

let disposeBag = DisposeBag()

strikes
    .skipWhile{ num in
            num % 2 == 0
    }
    .subscribe(onNext: { num in
        print("\(num)")
    })
    .addDisposableTo(disposeBag)

strikes.onNext(2)
strikes.onNext(2)
strikes.onNext(3)
strikes.onNext(4)
strikes.onNext(5)

strikes.onCompleted()

/* 列印結果
3
4
5
*/複製程式碼

到目前為止,上面的過濾操作都是基於一些靜態條件。如果現在你需要根據其它可觀察物件例項的行為進行過濾判斷怎麼辦呢?所以接下來將會介紹涉及多例項的動態判斷,其中最常見的就是 skipUntil 操作。該操作過程如下圖,上面兩行表示可觀察物件的生命週期而最下面的表示觀察者,直到第二行的可觀察物件傳送資料後第三行的觀察者才能接受到第一行傳送的資料。

06
06

圖示對應程式碼:

let strikes = PublishSubject<String>()
let trigger = PublishSubject<String>()

let disposeBag = DisposeBag()

strikes
    .skipUntil(trigger)
    .subscribe(onNext: { 
        print($0)
    })
    .addDisposableTo(disposeBag)

strikes.onNext("1")

trigger.onNext("X")

strikes.onNext("2")
strikes.onNext("3")

strikes.onCompleted()

/* 列印結果
2
3
*/複製程式碼

Take 過濾

這是一組與 Skip 相反的過濾操作。這組操作中最基礎的操作為 take ,該操作的過程完全與 skip 相反。下圖演示了 take(2) 操作的過程,它只會對前兩個事件進行響應而忽略後面的事件。

07
07

上圖對應程式碼:

let strikes = PublishSubject<String>()

let disposeBag = DisposeBag()

strikes
    .take(2)
    .subscribe(onNext: { 
        print($0)
    })
    .addDisposableTo(disposeBag)

strikes.onNext("1")
strikes.onNext("2")
strikes.onNext("3")

strikes.onCompleted()

/* 列印結果
1
2
*/複製程式碼

除此之外,skipWhile 也有對應的 Take 操作 takeWhile ,兩者的程式碼結構幾乎一致只不過前者是跳過操作而後者則是響應操作。不過這裡我不準備介紹 takeWhile 操作(可以自己動手試下),而是介紹 takeWhile 變種 takeWhileWithIndex。其實函式名已經表明了該操作的主要功能,在 takeWhile 的基礎上會加上索引 index 引數。因為有時候我們除了需要通過 value 進行過濾判斷外,索引 index 也可能是一個判斷維度。下圖就展示了 takeWhileWithIndex 簡單使用示例,對於 valueindex 值小於 1 的事件全部跳過。

08
08

圖示對應程式碼:

let strikes = PublishSubject<Int>()

let disposeBag = DisposeBag()

strikes
    .takeWhileWithIndex { integer, index in
        integer > 1 && index > 1
    }
    .subscribe(onNext: { 
        print( "\($0)")
    })
    .addDisposableTo(disposeBag)

strikes.onNext(1)
strikes.onNext(2)
strikes.onNext(3)

strikes.onCompleted()

/* 列印結果
3
*/複製程式碼

其實 Skip 組中同樣存在與 takeWhileWithIndex 相對的 skipWhileWithIndex ,感興趣可以自己檢驗一下。接下來我們介紹 Take 組中的最後一個操作 takeUntil 。同樣地該操作是 skipUntil 的反操作,直到另一個例項物件觸發後該例項物件的觀察者才會停止響應。下圖就是 takeUntil 操作的一個簡單示例,作為觀察者第三行會一直響應第一行可觀察物件傳送的資料,直到第二行物件觸發後才停止。

09
09

對應程式碼:

let strikes = PublishSubject<String>()
let trigger = PublishSubject<String>()

let disposeBag = DisposeBag()

strikes
    .takeUntil(trigger)
    .subscribe(onNext: { 
        print($0)
    })
    .addDisposableTo(disposeBag)

strikes.onNext("1")
strikes.onNext("2")

trigger.onNext("X")

strikes.onNext("3")

strikes.onCompleted()

/* 列印結果
1
2
*/複製程式碼

Distinct 過濾

最後本文將介紹 Distinct 過濾操作 distinctUntilChanged 。對於觀察者來說,有時可觀察物件可能在某段時間內連續發生相同的資料。假設這些資料與 UI 相關的話,那麼這裡就存在不必要的重新整理操作了。所以我們有必要對過濾這些連續的相同資料,減少不必要的響應操作。下圖就是一個簡單的示例,圖中我們過濾掉了相同的後續資料,只會對第一個作出響應。

10
10

對應示例程式碼:

let strikes = PublishSubject<String>()

let disposeBag = DisposeBag()

strikes
    .distinctUntilChanged()
    .subscribe(onNext: { 
        print($0)
    })
    .addDisposableTo(disposeBag)

strikes.onNext("1")
strikes.onNext("2")
strikes.onNext("2")
strikes.onNext("3")

strikes.onCompleted()

/* 列印結果
1
2
3
*/複製程式碼

總結

本文在前面的基礎上通過圖示和程式碼介紹了主要的過濾操作。掌握好這些操作有利於我們最大化的發揮 RxSwift 功力。當然文中的程式碼都非常簡單,所以我希望你在實際程式設計中不斷磨練。

原文地址

相關文章