前言
这篇文章从实际的代码上去分析,在RxSwift中为什么要使用Driver以及应该如何使用Driver
通过网络请求绑定UI
现在模拟一种常用的情况, 监听UI -> 请求网络 -> 更新UI
如下代码所示,这里是RxSwift的基本使用,监听textField的变化,请求网络,然后更新UI
//监听textField变化,发送网络请求
let result = inputTF.rx.text.skip(1)
.flatMap { [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
}.share(replay: 1, scope: .whileConnected)
//给label赋值,模拟更新某处UI
_ = result.subscribe(onNext: { (element) in
print("订阅到了1")
self.textLabel.text = element as? String
}, onError: { (error) in
})
//给Btn赋值,模拟更新另一处UI
_ = result.subscribe(onNext: { (element) in
print("订阅到了2")
self.btn.titleLabel?.text = element as? String
}, onError: { (error) in
})
//模拟网络请求,还模拟了在监测到错误输入后返回错误的请看
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: "com.error.cn", code: 10086, userInfo: nil))
}
//模拟网络请求发送时是子线程
DispatchQueue.global().async {
print("发送之前看看: \(Thread.current)")
ob.onNext("已经输入:\(inputText)")
ob.onCompleted()
}
return Disposables.create()
})
}
当我们运行代码后,发现有几处问题
-
self.textLabel.text = element as? String
会引起崩溃,因为我们是在子线程更新的UI,需要切换到主线程 - "请求网络了" 打印了两次,说明订阅了两次,发送了两个网络请求,这当然不是我们想要的结果
- 注释掉更新UI的代码,然后测试错误事件(输入 1234),发现在发生错误后,这个错误会取消所有绑定,当我们再输入一个新的数字后,也无法产生响应了
优化代码解决上面的问题
我们先用基本的RxSwift提供的函数解决这个问题,解决的函数看下面的注释
//监听textField变化,发送网络请求
let result = inputTF.rx.text.skip(1)
.flatMap { [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
.observeOn(MainScheduler()) //切换到主线程,解决问题1
.catchErrorJustReturn("检测到了错误事件") //捕获错误,解决问题3
.share(replay: 1) //共享网络请求,解决问题2
}
再次运行代码,发现解决了上述问题,一切正常,但是,我们能不能再优化代码呢?感觉上面的写法特别麻烦,而且在一个大型系统内,要确保每一步不被遗漏是一件不太容易的事情。所以更好的选择是合理运用编译器和特征序列来确保这些必备条件都已经满足。
所以这时就可以开始用RxSwift的Driver
特征序列了,毕竟是老司机序列,专为解决UI问题而生。
使用Driver进行再次优化
看看下面的代码,清爽干净,解决所有问题,Driver的使用按照如下代码就OK了
let result = inputTF.rx.text.orEmpty
.asDriver()
.flatMap {
return self.dealwithData(inputText: $0)
.asDriver(onErrorJustReturn: "检测到了错误事件")
}.map{ $0 as! String} //把Any转为String
//订阅代码修改为:
//给label赋值,模拟更新某处UI
result.drive(self.textLabel.rx.text)
//给Btn赋值,模拟更新另一处UI
result.drive(self.btn.rx.title())
分析一下上面的代码:
- asDriver 把监听序列转换成了Driver,任何可监听序列都可以被转换为 Driver,只要他满足 3 个条件:
- 不会产生 error 事件
- 一定在 MainScheduler 监听(主线程监听)
- 共享附加作用(就类似上面的订阅两次,但是只需要发一次网络请求)
- onErrorJustReturn: [] 捕获了错误
- 调用drive 直接把数据绑定到UI上。drive 方法只能被 Driver 调用。这意味着,如果你发现代码所存在 drive,那么这个序列不会产生错误事件并且一定在主线程监听。这样你可以安全的绑定 UI 元素。
探索Driver的原理
和之前的几篇文章一样,这里探索一下Driver的实现原理,不过我这里不写的那么详细了,大家可以自己尝试一下探索源码的乐趣。
沿着asDriver
点击进去,找到了它的方法实现,catchError
就是实现了Driver能捕获错误的功能了
public func asDriver(onErrorRecover: @escaping (_ error: Swift.Error) -> Driver<Element>) -> Driver<Element> {
let source = self
.asObservable()
.observeOn(DriverSharingStrategy.scheduler)
.catchError { error in
onErrorRecover(error).asObservable()
}
return Driver(source)
}
observeOn
订阅的线程DriverSharingStrategy.scheduler
是主线程MainScheduler
,通过点击进去找源码所发现的。所以Driver是在主线程跑的,可以更新UI
public static var scheduler: SchedulerType { return SharingScheduler.make() }
public private(set) static var make: () -> SchedulerType = { MainScheduler() }
点进Driver(source)
,发现Driver
是SharedSequence
的一个别名, 同时看到这里返回的是source.share(replay: 1, scope: .whileConnected)
,是不是和我们第一次优化那段代码时写的一样 ?
share会返回一个新的事件序列,监听底层 序列的事件,并且通知所有的订阅者。
在上面的表现就是订阅多次,也只会调用一次网络请求。
public typealias Driver<Element> = SharedSequence<DriverSharingStrategy, Element>
public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
return source.share(replay: 1, scope: .whileConnected)
}
继续探索一下share
,通过这里makeSubject: { ReplaySubject.create(bufferSize: replay)
,我们发现它创建了一个ReplaySubject
case .whileConnected:
switch replay {
case 0: return ShareWhileConnected(source: self.asObservable())
case 1: return ShareReplay1WhileConnected(source: self.asObservable())
default: return self.multicast(makeSubject: { ReplaySubject.create(bufferSize: replay) }).refCount()
}
然后我们沿着multicast
的方法一直往里找,发现它调用的是ConnectableObservableAdapter
里的初始方法,发现这里保存了这个subject,
然后这个subject是一个lazySubject
。
init(source: Observable<Subject.Observer.Element>, makeSubject: @escaping () -> Subject) {
self._source = source
self._makeSubject = makeSubject
self._subject = nil
self._connection = nil
}
fileprivate var lazySubject: Subject {
if let subject = self._subject {
return subject
}
let subject = self._makeSubject()
self._subject = subject
return subject
}
看到懒加载对象,突然就明白了这就是为什么订阅多次,只发一次网络请求的原因了,这里其实创建了一个新的Subject
来监听序列,这个Subject
只会创建一次,所以它可以监听多个,但是只执行一次。
总结
Driver 是一个精心准备的特征序列。它主要是为了简化 UI 层的代码,所以在开发中是能够比较常用到的,希望大家都能掌握并熟练使用。
网友评论