RxSwift 之过滤操作

456 阅读3分钟

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 功力。当然文中的代码都非常简单,所以我希望你在实际编程中不断磨练。

原文地址