这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
前言
其实有关调度器这个一节,我原本计划是不打算单独写文章做介绍的。
原因有二:
-
通过Rx框架,大部分时候你完全不用考虑使用调度器,按照流水线的思路去写即可,其他的让Rx给你去调度。
-
涉及异步操作的网络请求,其实已经有RxMoya了,它已经被封装的特别好了,省去了需要费心的地方。
但是当我看着调度器方法命名的时候,我确实觉得,这实在太太太容易造成误解了。所以还是来说说吧。
调度器
我们先上一个例子,接口请求,耗时操作,在全队队列去操作,完成后回主队列赋值数据。我们用GCD来实现一下:
/// 后台取得数据
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: URL(string: "https://www.wanandroid.com/banner/json")!
/// 主线程处理结果
DispatchQueue.main.async {
let string = String(data: data, encoding: .utf8)
print(string)
}
}
复制代码
思考一下,我们如何用一个序列来完成实现上线的情况,展示不考虑线程:
-
由于是网络请求,可以考虑使用序列的Single类型。
-
使用Single的工厂方法生成一个
Single<Data>
的序列。 -
这个
Single<Data>
使用subscribe方法中去实现处理结果。
为了便于理解,我们拆成两端代码:
/// 生成一个序列
let single = Single<Data>.create { single in
print("序列生成的线程")
print(Thread.current)
if let data = try? Data(contentsOf: URL(string: "https://www.wanandroid.com/banner/json")!) {
single(.success(data))
}else {
single(.error(SomeError()))
}
return Disposables.create()
}
/// 序列的处理结果
single.subscribe { data in
print("subscribe处理的线程")
print(Thread.current)
let string = String(data: data, encoding: .utf8)
print(string)
} onError: { _ in
}.disposed(by: disposeBag)
复制代码
在这里,我在生成序列的时候(Single<Data>.create
)打印了当前的线程,同时在订阅的时候(subscribe
)也打印了当前线程,我们来看看日志:
序列生成的线程
<NSThread: 0x2839b0e00>{number = 1, name = main}
subscribe处理的线程
<NSThread: 0x2839b0e00>{number = 1, name = main}
复制代码
可以看的处理,默认序列生成与数据处理都是在主线程中进行的。
这不符合我们的预期,我们希望生成序列的时候在一个非主线程中完成,怎么操作呢?
/// 生成一个序列
let single = Single<Data>.create { single in
print("序列生成的线程")
print(Thread.current)
if let data = try? Data(contentsOf: URL(string: "https://www.wanandroid.com/banner/json")!) {
single(.success(data))
}else {
single(.error(SomeError()))
}
return Disposables.create()
}
/// 注意这个方法名,subscribeOn,它表示指定生成序列的线程
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
复制代码
在生成序列的末尾添加一个方法,这个方法叫做subscribeOn、subscribeOn、subscribeOn。
重要的事情说三遍!!!
明明是我希望在指定线程生成序列,但是方法名却是subscribeOn
!
虽然我看了有点晕,但是确实如此。让我们看看,指定序列生成线程之后,日志的打印情况吧:
序列生成的线程
<NSThread: 0x2808f8e80>{number = 5, name = (null)}
subscribe处理的线程
<NSThread: 0x2808f8e80>{number = 5, name = (null)}
复制代码
咦?感觉有点不对啊,怎么序列生成的线程和subscribe处理的线程都变成了非主线程了,我还是希望subscribe处理的线程在主线程呀!
别急,我们在生成序列的subscribeOn
后面在追加一个方法:
let single = Single<Data>.create { single in
print("序列生成的线程")
print(Thread.current)
if let data = try? Data(contentsOf: URL(string: "https://www.wanandroid.com/banner/json")!) {
single(.success(data))
}else {
single(.error(SomeError()))
}
return Disposables.create()
}
/// 注意这个方法名,subscribeOn,它表示指定生成序列的线程
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
/// 表示被订阅的时候,在主线进行处理
.observeOn(MainScheduler.instance)
复制代码
真正指定在subscribe中处理序列的函数叫做observeOn,它表示single.subscribe { data in }
所在的线程,我们再来看看此时打印的线程信息:
序列生成的线程
<NSThread: 0x280b199c0>{number = 4, name = (null)}
subscribe处理的线程
<NSThread: 0x280b48e00>{number = 1, name = main}
复制代码
参考文档
很好,达到预期效果,序列在非主线程完成,subscribe处理的主线程完成。
总结
我为啥有写这篇文章?因为指定序列生成线程的函数、指定订阅序列处理线程的函数的命名实在有些奇怪,特别是序列生成线程的函数,简直就望文生义了,结果啪啪啪的打脸。
注意事项:
-
不要被函数的名字干扰了其功能。
-
一般情况下序列的生成与序列的订阅处理都在主线程中。
-
如果序列的生成指定了线程,最好订阅处理也指定线程,否则会序列的生成指定线程也会是订阅处理的线程。
-
经过验证,如果订阅处理指定了线程,而序列的生成没有做设置,并不会对序列的生成的线程有影响,依旧是主线程生成:
let single = Single<Data>.create { single in
print("序列生成的线程")
print(Thread.current)
if let data = try? Data(contentsOf: URL(string: "https://www.wanandroid.com/banner/json")!) {
single(.success(data))
}else {
single(.error(SomeError()))
}
return Disposables.create()
}
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
复制代码
日志打印:
序列生成的线程
<NSThread: 0x2803bce00>{number = 1, name = main}
subscribe处理的线程
<NSThread: 0x2803e9740>{number = 4, name = (null)}
复制代码
求指导
其实Rx中,在哪些业务下面要使用调度器我确实不了解,还希望有掘友能指点一二,感谢!
另外想说的是,可能就我觉得这命名有问题,大伙都觉得挺好的,那么就让大家见笑了。
结语
我在掘金里以RxSwift为关键词搜索了文章并进行粗略的阅读,感觉自己写的有点班门弄斧,人家都是在深入底层,而我只是在API上面打仗。
很惭愧,需要更多的学习与沉淀。
RxSwift相关的,我估计暂时不会继续更文了。
RxSwift编写wanandroid客户端现已开源
目前RxSwift编写wanandroid客户端已经开源了——项目链接。