这篇文章是对上一篇 一些 RxSwift 思考题 的解答,如果您对这篇解答有什么疑问或者错误的地方要指出来,欢迎直接在下面评论,我会及时的回复和修正。
思考这些问题可以帮助你更好的理解什么是订阅、Observable
有什么特性、如何比较优雅地写 Rx 代码。
使用 flatMapLatest
即可,不考虑上一个 flatMap
的 Observable
,每当有新的值过来时,都立即进行 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
中还是有输出。
使用 withLatestFrom
和 flatMap
都是可以的。先来看 withLatestFrom
:
从上图中可以看出这应该就是我们所需要的了,没毛病。
再来看 flatMap
:
可以看到使用 flatMap
也是可以的(我们是通过点击 button 时订阅 currentValue
完成的)。但你应该注意到了,我将 currentValue
从 PublishSubject
换成 ReplaySubject
,以及在 flatMap
操作后面添加了一个 take(1)
。
二者区别主要在于 withLatestFrom
和 flatMap
的订阅时机不同,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
。
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)
}
}
}
invokeMethod
和 sendMessage
的区别sentMessage
和 methodInvoked
只有一个区别,sentMessage
会在调用方法前发送值, methodInvoked
会在调用方法后发送值。
比如,发送一个网络请求后,点击 button 可以重复发送一个该网络请求。创建一个操作符以复用。
extension ObservableConvertibleType {
func repeatWhen<O: ObservableType>(_ other: O) -> Observable<E> {
return other.map { _ in }
.startWith(())
.flatMap { () -> Observable<E> in
self.asObservable()
}
}
}