一些 RxSwift 思考题 - 回答

这篇文章是对上一篇 一些 RxSwift 思考题 的解答,如果您对这篇解答有什么疑问或者错误的地方要指出来,欢迎直接在下面评论,我会及时的回复和修正。

思考这些问题可以帮助你更好的理解什么是订阅、Observable 有什么特性、如何比较优雅地写 Rx 代码。

# 取消一个请求

# 请求一个列表数据时,切换新的分类,取消上一次的网络请求(如果未完成)。该使用什么操作符?

使用 flatMapLatest 即可,不考虑上一个 flatMapObservable ,每当有新的值过来时,都立即进行 flatMap 操作(并抛弃上一个 Observable)。

示例代码大概如下:

enum TopChart {
  case paid
  case free
  case topGrossing
}

let segmentedControl = UISegmentedControl()

segmentedControl
    .rx.selectedSegmentIndex.asDriver()
    .map { index in mapToTopChart(index) }
    .flatMapLatest { topChart in someRequest(topChart) }
# 单纯地发送一个请求,如何取消该网络请求?该使用什么操作符?

使用 takeUntil ,当 takeUntil 中的 Observable 发射一个时,结束对应的 Observable

let cancelbutton = UIbutton()

someRequest()
    .takeUntil(cancelbutton.rx.tap)

结合到实际情况(添加 flatMap 等操作后),代码大概如下:

segmentedControl
    .rx.selectedSegmentIndex.asDriver()
    .map { index in mapToTopChart(index) }
    .flatMapLatest { topChart in
      someRequest(topChart).takeUntil(cancelbutton.rx.tap.asDriver())
    }

# 确保获取最新的结果

# 在进行一些操作时,我们可能需要获取某些最新的值,比如一个下单页面,点击确定,总是应当带上订单最新的数据。该使用什么操作符?可以使用 combineLatest 吗?可以使用 withLatestFrom 吗?可以使用 flatMap 吗?
# 如果可以,他们有什么区别吗?

combineLatest 应该是不可以的,当我们 combine 了 button 的点击和下单的结果变动,此时每当有一个 Observable 发射一个值时,combineLatest 都会发射一个值。这应该不是我们想要的。我们期待的是,每当点击 button 后,才发射一个值。

从上图中我们可以看到在点击 button 后,currentValue 有变化时,result 中还是有输出。

使用 withLatestFromflatMap 都是可以的。先来看 withLatestFrom

从上图中可以看出这应该就是我们所需要的了,没毛病。

再来看 flatMap

可以看到使用 flatMap 也是可以的(我们是通过点击 button 时订阅 currentValue 完成的)。但你应该注意到了,我将 currentValuePublishSubject 换成 ReplaySubject ,以及在 flatMap 操作后面添加了一个 take(1)

二者区别主要在于 withLatestFromflatMap 的订阅时机不同,withLatestFrom 是立即被订阅,并发射了个值 1 ,而 flatMap 是在点击 button 之后才被订阅。

由于 flatMap 的订阅相对较晚,如果使用 PublishSubject 则可能收不到当前的值,需要使用诸如 ReplaySubject 做一个值的缓存。为了忽略点击 button 后又修改了值的情况,我加入了 take(1) 只获取第一个数据。

不注意这些区别,在使用时,可能会出现一些不预期影响。比如使用 withLatestFrom 时,currentValue 立即被订阅,相关代码立即执行。假设 currentValue 是个网络请求,这个网络请求则会在点击 button 之前执行,这可能不是我们想要的。

此外两种使用方法还可以帮我们做表单填写验证,分别对应点击 button 前验证、和点击 button 后验证两种场景。

# 多个异步请求

# 依次发送两个请求,第二个请求需要第一个请求的参数和响应结果,该如何组织操作符?列举几种?

# 如何跟踪上述请求状态?该使用什么操作符?

需要使用 using 操作符(当然你也可以使用 do 之类可以监听 Observable 生命周期的操作符)。

以一个网络请求为例,URLSession.share.rx.request(someRequest),当它被订阅时发送网络请求,网络请求完成后,生命周期结束。网络请求的状态刚好的 Observable 的声明周期对应上。

RxExample 中有对 using 操作符使用的示例: https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Services/ActivityIndicator.swift。

上图是对两个连续网络请求使用的示例。

# 重复请求

# 点击一个按钮发送一个请求,当该请求完成前,该按钮点击都不应该继续发送请求。该使用什么操作符?

flatMapFirst

# Cell 复用怎么办

# 遇到过 Cell 中使用 button 重复订阅问题吗?如何解决比较好?
# 解决复用后,将一个 button 点击绑定到一个 PublishSubject 可能会有什么问题?

为 Cell 添加一个 DisposeBag,在 prepareForReuse 时,更换新的 DisposeBag

var disposeBag = DisposeBag()

override func prepareForReuse() {
    super.prepareForReuse()
    disposeBag = DisposeBag()
}

使用是大概如下:

cell.button.rx.tap.subscribe().dispose(by: cell.disposeBag)

在这里用到一个 PublishSubject 可能会导致下一次重用时失效:

let publishSubject = PublishSubject<()>()
// ...

cell.button.rx.tap.bindTo(publishSubject).dispose(by: cell.disposeBag)

需要做如下修改:

cell.button.rx.tap
  .subscribe(onNext: {
    publishSubject.onNext(())
  })
  .dispose(by: cell.disposeBag)

# flatMap 同一个实例会怎么样

正常地订阅一次。

let value = Observable.just(1)

button.rx.tap.asObservable()
  .flatMap { value }

这里每次点击 button 都会收到一个 1

# 创建一个 Observable ,每当订阅该 Observable 都将发送一个请求,每次请求带上一个的随机的 uuid

请配合 RxCocoa 提供的 URLSession 扩展方法完成。

使用 defer 是比较好的方式:

Observable.just 替换成 URLSession 即可。

# 检查输入结果是否和最初第一个值相同

创建一个操作符,可以检查输入结果是否和最初的一样。比如一个 TextField 最初是 text1 ,经过一顿乱输,如何判断最终输入结果是否和最初相同?请尽量复用该操作符到各个场景。

extension ObservableConvertibleType where E: Equatable {

    func isEqualOriginValue() -> Observable<(value: E, isEqualOriginValue: Bool)> {
        return self.asObservable()
            .scan(nil as (origin: E, current: E)?, accumulator: { acc, x -> (origin: E, current: E)? in
                if let acc = acc {
                    return (origin: acc.origin, current: x)
                } else {
                    return (origin: x, current: x)
                }
            })
            .map { diff -> (value: E, isEqualOriginValue: Bool) in
                return (diff!.current, isEqualOriginValue: diff!.origin == diff!.current)
        }
    }

}

# invokeMethodsendMessage 的区别

sentMessagemethodInvoked 只有一个区别,sentMessage 会在调用方法前发送值, methodInvoked 会在调用方法后发送值。

# 如何重复执行某个操作?

比如,发送一个网络请求后,点击 button 可以重复发送一个该网络请求。创建一个操作符以复用。

extension ObservableConvertibleType {

    func repeatWhen<O: ObservableType>(_ other: O) -> Observable<E> {
        return other.map { _ in }
            .startWith(())
            .flatMap { () -> Observable<E> in
                self.asObservable()
        }
    }

}