前言
获取网络数据展示在UI界面上,可以说App上的过半需求都是这样的,实现这个过程方式有很多种,今天我们就借这个功能来讲讲Driver
的使用,看看Driver带来的哪些优点。
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()
})
}
示例图
为了实现当输入改变时后,多个地方同时请求同一个接口数据的过程,即同时订阅多次
(此处请求两次),利用RxSwift框架的三种方式完成。
2、常规方案
let result = inputTF.rx.text.skip(1)
//序列中的序列
.flatMap { input in
return self.dealWithData(inputText: input!)
}
_ = result.subscribe({ element in
print("订阅到了\(element)")
})
_ = result.subscribe({ element in
print("订阅到了\(element) - \(Thread.current)")
//更新UI
})
flatMap
:此操作符会对源Observable的每一个元素应用一个转换方法,将它们转换成Observables。然后将这些Observables的元素合并后再发送出来,即又将其合成一个Observable序列。比如当Observable的元素本身拥有其他的Observable时,我们可以将所有子Observables的元素发送出来。
如上代码中,当进行两次订阅时,内部序列会按先后顺序将信号返回出来。
此时的打印情况如下:
抛出错误
可见的确是分别进行了两次网络请求,并且两次都有返回,网络请求在主线程,发送之前是在子线程发出去的,然后打印结果也是在子线程,那么我们就能发现一下几个问题:
当订阅几次时,会进行几次网络请求,但数据都是一样的,会造成网络资源的浪费。
如果我们在最后返回时候如果要进行UI刷新的话,在子线程就会报错甚至崩溃。
错误事件的处理,当输入1234时,会返回错误,后续订阅就失效,订阅就断开,再输入其他都没用了。这肯定不行的。
3、优化后方案
let result = inputTF.rx.text.skip(1)
//序列中的序列
.flatMap { input in
return self.dealWithData(inputText: input!)
.observe(on: MainScheduler())
.catchAndReturn("监测到错误事件")//error起死回生
}
.share(replay: 1, scope: .whileConnected)
_ = result.subscribe({ element in
print("订阅到了\(element)")
})
_ = result.subscribe({ element in
print("订阅到了\(element) - \(Thread.current)")
//更新UI
})
打印结果如下:
方案二正常结果
方案二返回错误
请求网络只有一次,订阅后收到信号是在主线程,错误信号返回后,订阅不会中断,还可以继续运行。
添加了三句代码,增加三个操作,解决以上三个问题。
-
.share(replay: 1, scope: .whileConnected)
控制请求只有一次,达到共享网络数据目的。 -
.observe(on: MainScheduler())
控制信号返回后是在主线程,这样刷新UI等操作不会报错。当然你也可在返回时候调用DispatchQueue.main.async{}
,但相对而言.observe(on方式更符合RxSwift用法。 -
.catchAndReturn("xxxxxxx ")
处理error信号,让error信号起死回生。
4、最佳推荐方案
let result = inputTF.rx.text
.asDriver()
.flatMap {
return self.dealWithData(inputText: $0!)
.asDriver(onErrorJustReturn: "监测到错误事件")
}
_ = result.map { "长度:\(($0 as! String).count)" }
.drive(self.textLabel.rx.text)
_ = result.map { "\($0)" }
.drive(self.btn.rx.title())
本方案采用Driver方式,asDriver()
将源序列转化为一个SharedSequence类型的结构体实例,通过.drive
方法来订阅信号(类似bind(to:)),就很完美滴解决了上面三个问题。
请求网络:<_NSMainThread: 0x60000399c040>{number = 1, name = main}
发送之前:<NSThread: 0x6000039a1400>{number = 5, name = (null)}
接下来就看看Driver到底是什么?到底做了什么?
首先看看asDriver():
self.asDriver
注意此处生成的source即外界序列本身(
Observable->Driver->Observable
),并留意此处的DriverSharingStrategy
类型的结构体,后面肯定是需要用到这个结构体的。查看DriverSharingStrategy.scheduler,
public static var scheduler: SchedulerType { SharingScheduler.make() }
public private(set) static var make: () -> SchedulerType = { MainScheduler() }
看到MainScheduler,那是不是当前序列就是在这里设置运行的线程的呢?
接着查看Driver的初始化:
driver初始化
asDriver()
将序列转换为Driver类型,所以flatMap里面需要返回一个Driver类型的,所以使用到了.asDriver(
将加载数据但会结果转换为Driver类型。
接着我们来看看订阅方法.drive():
这里面对当前任务是不是在主线程中运行进行了校验,着重看下self.asSharedSequence()方法:
asSharedSequence
发现有两个地方,我们先看第一个,发现是
SharedSequenceConvertibleType
协议里面的方法声明,且协议里有SharingStrategy关联类型:SharedSequenceConvertibleType
返回查看第二个asSharedSequence使用的地方:
asSharedSequence2
发现是在刚刚Driver初始化的结构体里面,即将Observable转换为Driver类型。
并注意此处序列的
.subscribe()
,所以在.drive里面有订阅的相关封装。到这里还没有看到SharingStrategy的真正类型是什么...
全局搜索SharingStrategyProtocol协议,发现刚刚的
DriverSharingStrategy
遵循了此协议(有三处用到,我们直接来此查看)。且实现了share
协议方法,断点调试的确来到这里:DriverSharingStrategy
结合前面使用了此处的scheduler变量,我们可以确定SharingStrategy实际就是
DriverSharingStrategy
类型。方式二
:也可使用lldb打印查看SharingStrategy的真正类型:SharingStrategy类型
即SharingStrategy类型为DriverSharingStrategy。
- DriverSharingStrategy.share方法里面正是
.share(replay: 1, scope: .whileConnected)
,即解决第一个数据共享的问题的代码。 - 那么引刃而解上方的
scheduler
即为解决第二个主线程UI刷新的问题。 -
.asDriver(onErrorJustReturn: "xxxxxx")
即解决了第三个错误处理的问题。
所以在解决多个信号同时订阅一个信号的功能,Drvier提供了更方便的使用,规避了一些资源及错误处理等的问题。
网友评论