RXSwift在项目中实战总结: Subject, Driver, 高阶函数
1.Subject
Subject 既攻也受,它既可以作为序列,又可以作为观察者。下面我们看一下既攻也守的原理:
public protocol SubjectType : ObservableType {
/// The type of the observer that represents this subject.
///
/// Usually this type is type of subject itself, but it doesn't have to be.
associatedtype Observer: ObserverType
/// Returns observer interface for subject.
///
/// - returns: Observer interface for subject.
func asObserver() -> Observer
}
1)SubjectType
继承了ObservableType
,具有序列的特性
2)又通过associatedType关联了ObserverType,具有了观察者的特性
3)通过asObserver方法,可以直接从序列转化为观察者
1.最常用的Subject 1: BehaviorRelay
在5.0之前的版本最常用的是Variable,但是后来被废弃了,现在由BehaviorRelay代替,现在最常用的就是BehaviorRelay。BehaviorRelay的特性:
I. 可以存储一个信号(也就是我们常说的默认值)
II. 随时订阅响应(也就是BehaviorRelay的值一旦发生变化,就会立即被订阅到)
III. 注意:响应发送的时候使用的方法是accept方法: behaviorRelay.accept(300)
III.替换原来的Variable
示例代码:
func testBehaviorRelay() {
let behaviorRelay = BehaviorRelay(value: 300)
behaviorRelay.subscribe(onNext: { (num) in
print("订阅\(num)")
}).disposed(by: disposeBag)
print("打印:\(behaviorRelay.value)")
behaviorRelay.accept(3000)
}
打印结果:
订阅300
打印300
订阅3000
注意:初始化值300也是可以订阅到的
2.最常用的Subject 2: PublishSubject
PublishSubject 可以不需要初始来进行初始化(也就是可以为空),并且它只会向订阅者发送在订阅之后才接收到的元素。
func testPublishSubject() {
// 1:初始化序列
let publishSub = PublishSubject<Int>() //初始化一个PublishSubject 装着Int类型的序列
// 2:发送响应序列
publishSub.onNext(1)
// 3:订阅序列
publishSub.subscribe {
print("订阅到了:",$0)
}.disposed(by: disposeBag)
// 再次发送响应
publishSub.onNext(2)
publishSub.onNext(3)
}
订阅到了: next(2)
订阅到了: next(3)
注意:信号1没有被订阅到,只接受订阅之后的响应
2.Driver
首先介绍两个概念,RXSwif的防抖与节流:比如在日常开发中, scroll、click、reload等高频率的触发事件,会过度损耗页面性能,导致页面卡顿,页面抖动。有时我们不希望在事件持续触发的过程中那么频繁地去执,此时防抖和节流是比较好的解决方案。
防抖:比如TextField一直输入时, 忽略 2 秒内的响应, 停止输入 2 秒后响应一次
userNameTextField.rx.text
.debounce(RxTimeInterval.seconds(2), scheduler: MainScheduler.instance)
.subscribe(onNext: { str in
let date = Date()
let format = DateFormatter()
format.dateFormat = "HH:mm:ss"
print("RX的debounce: \(format.string(from: date)) - \(str ?? "")")
}).disposed(by: disposeBag)
输出:
RX的debounce: 17:53:22 -
RX的debounce: 17:53:24 - 111
节流:比如TextField一直输入时, 每 2 秒触发一次
// 节流 (TextField一直输入时, 每 2 秒触发一次)
userName.rx.text
.throttle(RxTimeInterval.seconds(2), scheduler: MainScheduler.instance)
.subscribe(onNext: { str in
let date = Date()
let format = DateFormatter()
format.dateFormat = "HH:mm:ss"
print("throttle: \(format.string(from: date)) - \(str ?? "")")
}).disposed(by: disposeBag)
输出:
throttle: 18:02:51 - Optional("")
throttle: 18:02:53 - Optional("1")
throttle: 18:02:55 - Optional("11111")
介绍Driver:
Driver
是一个精心准备的特征序列。它主要是为了简化 UI
层的代码,比如获取网络数据展示在UI界面上,今天我们就对这个功能来讲讲Driver
的使用。因为它具有以下Driver的特征:
I. 不会产生 error
事件
II. 一定在 MainScheduler
监听(主线程监听)
III. 会共享附加作用
1. 网络请求代码
func dealWithData(inputText: String)-> Observable<Any> {
print("请求网络:\(Thread.current)")
return Observable<Any>.create({ ob -> Disposable in
if inputText == "1234" {
ob.onError(NSError.init(domain: "LcrError", code: 10085, userInfo: nil))
}
DispatchQueue.global().async {
print("发送之前:\(Thread.current)")
ob.onNext("已经输入:\(inputText)")
ob.onCompleted()
}
return Disposables.create()
})
}
输出:
请求网络:<_NSMainThread: 0x6000027705c0>{number = 1, name = main}
发送之前:<NSThread: 0x600002752600>{number = 5, name = (null)}
next(已经输入:123456)
completed
接下来再介绍RXSwift的三个高阶函数:
I. RXSwift的skip函数:从源可观察序列发出元素,直到跳过skip设置的个数再开始响应订阅。这个应用非常频繁 ,比如textField被点击但是还未输入文字的时候,不使用skip(1),就会被订阅到空字符串,这是不需要的,还有其他一些可观察序列的初始化过程是需要要订阅到的。
func testSkipMethod() {
Observable.of(1, 2, 3, 4, 5, 6)
.skip(2)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
}
输出:
2
3
4
5
6
II. RXSwift的map函数:map函数闭包应用于可观察序列发出的元素,并返回转换后的元素组成的新可观察序列。
let ob = Observable.of(1,2,3,4)
ob.map { (number) -> Int in
return number+2
}.subscribe{
print("\($0)")
}.disposed(by: disposeBag)
输出:
next(3)
next(4)
next(5)
next(6)
completed
III. RXSwift的flatMap函数:将可观察序列发射的元素转换为可观察序列,并将两个可观察序列的发射合并为一个可观察序列。比如此操作符会对源Observable的每一个元素应用一个转换方法,将它们转换成Observables。然后将这些Observables的元素合并后再发送出来,即又将其合成一个Observable序列。比如当Observable的元素本身拥有其他的Observable时,我们可以将所有子Observables的元素发送出来。
let result = userNameTextField.rx.text.skip(1).flatMap { input in
return self.dealWithData(inputText: input!)
}
result.subscribe({ element in
print("订阅到了\(element)")
}).disposed(by: disposeBag)
result.subscribe({ element in
print("订阅到了\(element) - \(Thread.current)")
//更新UI
}).disposed(by: disposeBag)
输出:
请求网络:<_NSMainThread: 0x6000030140c0>{number = 1, name = main}
请求网络:<_NSMainThread: 0x6000030140c0>{number = 1, name = main}
发送之前:<NSThread: 0x6000030b1300>{number = 10, name = (null)}
订阅到了next(已经输入:11)
发送之前:<NSThread: 0x6000030377c0>{number = 11, name = (null)}
订阅到了next(已经输入:11) - <NSThread: 0x6000030377c0>{number = 11, name = (null)}
由上面输出结果可以看出分别进行了两次网络请求,并且两次都有返回,网络请求在主线程,发送之前是在子线程发出去的,然后打印结果也是在子线程,那么我们就能发现如下几个问题:
I. 当订阅几次时,会进行几次网络请求,但数据都是一样的,会造成网络资源的浪费
II. 如果我们在最后返回时候如果要进行UI刷新的话,在子线程就会报错甚至崩溃
III. 错误事件的处理,当输入1234时,会返回错误,后续订阅就失效,订阅就断开,再输入其他都没用了。
2.进一步优化后的代码:
let result = userName.rx.text.skip(1)
.flatMap { input in
return self.dealWithData(inputText: input!)
.observe(on: MainScheduler())
.catchAndReturn("监测到错误事件")
}
.share(replay: 1, scope: .whileConnected)
result.subscribe({ element in
print("订阅到了\(element)")
}).disposed(by: disposeBag)
result.subscribe({ element in
print("订阅到了\(element) - \(Thread.current)")
//更新UI
}).disposed(by: disposeBag)
输出:
请求网络:<_NSMainThread: 0x600001ccc040>{number = 1, name = main}
发送之前:<NSThread: 0x600001c55780>{number = 8, name = (null)}
订阅到了next(已经输入:11)
订阅到了next(已经输入:11) - <_NSMainThread: 0x600001ccc040>{number = 1, name = main}
优化后的代码问题得到明显的解决,多个订阅只有一次网络请求,订阅后收到信号是在主线程,更新UI不会发生错误,错误信号返回后,订阅不会中断,还可以继续运行。
I. share(replay: 1, scope: .whileConnected)
控制多次订阅网络请求只有一次,达到共享网络数据目的
II. observe(on: MainScheduler())
控制信号返回是在主线程,这样更新UI等要求在主线程的操作不会发生错误
III. catchAndReturn("监测到错误事件")
处理error信号, 保证在遇到错误情况后不会停止订阅信号。
3.关于这个问题的最优解:Driver
let result = userName.rx.text
.asDriver()
.flatMap {
return self.dealWithData(inputText: $0!)
.asDriver(onErrorJustReturn: "监测到错误事件")
}
result.map { "输入字符串长度:\(($0 as! String).count)" }
.drive(userNameTip.rx.text).disposed(by: disposeBag)
result.map { "\($0)" }
.drive(passwordTip.rx.text).disposed(by: disposeBag)
我们可以看到,使用Driver简洁高效的完成了上面的功能,接下来我们看一下Driver的底层原理
1)点击.asDriver(onErrorJustReturn: "监测到错误事件")进入底层源码
public func asDriver(onErrorJustReturn: Element) -> Driver<Element> {
let source = self
.asObservable()
.observe(on:DriverSharingStrategy.scheduler)
.catchAndReturn(onErrorJustReturn)
return Driver(source)
}
2)我们可以看到这段代码里实现了 catchAndReturn(onErrorJustReturn) 错误处理方法,再点击 .observe(on:DriverSharingStrategy.scheduler) 里的 DriverSharingStrategy.scheduler 进入源码
public struct DriverSharingStrategy: SharingStrategyProtocol {
public static var scheduler: SchedulerType { SharingScheduler.make() }
public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
source.share(replay: 1, scope: .whileConnected)
}
}
3)再点击 SharingScheduler.make() 进入源码
public enum SharingScheduler {
/// Default scheduler used in SharedSequence based traits.
public private(set) static var make: () -> SchedulerType = { MainScheduler() }
......
}
从上面我们可以看到,Driver订阅信号的操作是在 MainScheduler 主线程。
结语:
RXSwift在项目中经常使用的类和方法已经大体介绍完了,如果感觉有用的话,点个Star吧!