一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
- 本文主要介绍RxSwift中的
Driver的使用和原理,对于driver本质上就是我们的序列进行封装,通常我们操作UI的序列会使用driver。
1. 使用场景
我们模拟我们输入框输入进行搜索,实时变化搜索的场景,类似我们点外卖搜索。
首先看下下面的代码,我们定义下模拟网络请求,同时我们添加逻辑判断当输入1234的时候发送错误事件,其他情况下我们在全局异步函数下发送我们模拟请求的数据。
func dealwithData(inputText:String)-> Observable<Any>{
print("请求网络了 \(Thread.current)") // data
return Observable<Any>.create({ (ob) -> Disposable in
if inputText == "1234" {
ob.onError(NSError.init(domain: "密码过于简单", code: 400, userInfo: nil))
}
DispatchQueue.global().async {
print("发送之前看看: \(Thread.current)")
ob.onNext("已经输入:\(inputText)")
ob.onCompleted()
}
return Disposables.create()
})
}
我们在输入框中添加rx源序列,我们想要实现的效果是输入的时候进行请求,把请求的结果进行展示。正常情况下我们这样操作
let result = inputTF.rx.text.orEmpty.skip(1)
.flatMap { [ weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText: input))!
}
result.subscribe(onNext:{(str) in
print("订阅到:\(str)")
}).disposed(by: disposeBag)
result.subscribe(onNext:{(str) in
print("订阅到:\(str)-\(Thread.current)")
}).disposed(by: disposeBag)
我们订阅2次,实时监听我们的输入框的内容。打印结果如下
我们可以发现我们订阅的时候发现会进行多次请求,很明显不符合我们的要求,我们希望达到共享的请求数据。其次我们如果在订阅中更新UI线程会造成问题
我们因此需要进行修复下,我们的源序列在转换后需要调度在主线程,同时我们也要使用throttle来添加限制条件。throttle:指定了0.5,所以在0.5以内,只接收了第一条和最新的数据。
let result = inputTF.rx.text.orEmpty.skip(1)
.throttle(DispatchTimeInterval.milliseconds(500), scheduler: MainScheduler())
.flatMap { [ weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText: input))!
.observe(on: MainScheduler())
}
打印结果:
我们继续解决关于共享的问题
let result = inputTF.rx.text.orEmpty.skip(1)
.throttle(DispatchTimeInterval.milliseconds(500), scheduler: MainScheduler())
.flatMap { [ weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText: input))!
.observe(on: MainScheduler())
}.share()
我们继续处理下错误事件
let result = inputTF.rx.text.orEmpty.skip(1)
.throttle(DispatchTimeInterval.milliseconds(500), scheduler: MainScheduler())
.flatMap { [ weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText: input))!
.observe(on: MainScheduler())
.catchAndReturn("检测到了错误事件")
}.share()
我们自己可以处理错误事件
但是我们监测到错误后无法继续了,发送了dispose事件。我们可以使用share(replay: 1, scope: .whileConnected)来解决,其中replay表示缓冲区的最大元素计数,这里表示一次error事件。scope表示共享范围
2. 使用Driver
我们使用Driver进行处理
let result = inputTF.rx.text.orEmpty
.asDriver()
.flatMap {
return self.dealwithData(inputText: $0)
.asDriver(onErrorJustReturn: "检测到了错误事件")
}
result.map { "长度: \(($0 as! String).count)"}
.drive(self.textLabel.rx.text)
.disposed(by: disposeBag)
result.map { "\($0 as! String)"}
.drive(self.btn.rx.title())
.disposed(by: disposeBag)
result.drive(onNext:{(str) in
print("1订阅到:\(str)")
self.textLabel.text = (str as! String)
}).disposed(by: disposeBag)
result.drive(onNext:{(str) in
print("2订阅到:\(str)-\(Thread.current)")
}).disposed(by: disposeBag)
这里我们把序列转化为Driver序列,在订阅的时候我们使用drive替代subscribe,打印结果。
我们可以发现解决了我们的问题,我们它请求一次网络达到了共享序列的效果,其次我们可以发现UI的事件是在主线程上,不用我们手动调度;最后我们的error事件发送后还可以继续接受事件。
result.map { "长度: \(($0 as! String).count)"}
.drive(self.textLabel.rx.text)
.disposed(by: disposeBag)
result.map { "\($0 as! String)"}
.drive(self.btn.rx.title())
.disposed(by: disposeBag)
我们可以使用类似bind的效果使用drive进行绑定。
3. 原理解析
首先我们看下为何可以调用
可以发现对于ControlProperty,ControlEvent,BehaviorRelay的拓展可以转换为Driver。
Driver源码:
通过描述我们可以知道具有下面功能
- 不会
失败 - 处理回调在主线程
MainScheduler.instance上 - 具有
share策略
我们依次分析下它具有这些能力的原因,我们通常会把序列通过asDriver()来变成Driver<Element>序列。因此我们看下这个方法
3.1 ObservableConvertibleType拓展
我们看下ObservableConvertibleType的拓展关于driver的一些便捷方法
主要分为3种,主要是通过高阶函数组合的方式把我们的错误事件进行处理,因此对于driver序列不会失败也是可以解释了。因为它已经帮我们处理了error的事件。
对于我们没有指定错误处理,会走最后一个方法。可以看到对我们原有的源做了处理,让它订阅在主线程上,同时捕获处理错误事件。之后根据源初始化Driver,调用Driver(source)
类型是SharedSequence类型,查看其初始化
3.2 主线程调度
DriverSharingStrategy
对于结构体它具有scheduler属性,会生成一个默认主线程的调度环境
继续看下
MainScheduler的init
关于主线后面会在调度的时候分析。
3.3 订阅
相比我们普通的序列使用subscribe进行订阅,这里是对其进行了一层封装。使用drive
之后在调用我们普通序列的事件回调。对于我们绑定事件的话则是类似我们序列的bind
查看其实现:
4. 总结
对于Dirver我们可以看到它时对普通的序列进行一些高阶函数的封装,使它具备了共享序列源,错误处理,主线程调度的功能。这些很好的为我们关于UI方面的操作,因此作者也是基于对ObservableConvertibleType协议作出了拓展。以下为driver的大概流程: