RxSwift的对于序列的调度,在核心探究的那一章并没有明确的介绍,只是带过,所以另开一章来介绍。
首先来看问题,这是RxSwift的一个并发执行的序列
上图中有100个elements,都会在子线程执行。
问题一:线程是怎么创建的?
问题二:100个element会依次打印,怎么确保顺序?
带着问题去探究,这样会更好的理解其中的原理。
第一个问题,线程的创建,在subscrbe订阅之前,有一个特殊的处理
.subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background))
这里subscribe(on:)传入了一个ConcurrentDispatchQueueScheduler类型的参数
在这个方法我们可以明确的看到,这里返回了一个新的序列,而这个序列就是SubscribeOn类型的一个序列,有了这个概念之后,之后的subscribe方法订阅的则是SubscribeOn这个中间序列这个就很好理解了。
重点是这里传入的参数ConcurrentDispatchQueueScheduler。所以我们优先来看一下这个参数的内容是什么
在这里可以明确的看到,这是一个便利构造方法,这里调用自己的init方法,需要的参数则为一个DispatchQoS类型的参数,这里顺带说一下DispatchQoS。
DispatchQoS 为调度优先级 五种类型
static let userInteractive: DispatchQoS
用户交互任务的服务质量类,例如动画、事件处理或应用程序用户界面的更新。
static let userInitiated: DispatchQoS
阻止用户主动使用您的应用程序的任务的服务质量等级。
static let `default`: DispatchQoS
默认的服务质量等级。
static let utility: DispatchQoS
用户不主动跟踪的任务的服务质量等级。
static let background: DispatchQoS
您创建的维护或清理任务的服务质量等级。
static let unspecified: DispatchQoS
缺乏服务质量等级。
上图中的调用自己的init的方法时候,需要一个DispatchQueue队列的参数,所以在这里使用传入的参数DispatchQoS 来创建一个DispatchQueue队列,这里使用的也是一个便利构造方法
init(label: String, qos: DispatchQoS, attributes: DispatchQueue.Attributes, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency, target: DispatchQueue?)
这里的DispatchQueue.Attributes 如果为空,则为串行队列
这里的意思就为创建一个名称为rxswift.queue.(qos名称)的调度队列当作参数传给ConcurrentDispatchQueueScheduler的init方法,后面的另外一个参数leeway为间隔的时间,这里默认值为0纳秒,通过init方法,保存configuration这个属性。configuration 又是什么呢?
这是一个保存了队列和间隔时间的结构体,所以在这一步就是创建了一个队列和间隔时间的参数放在了SubscribeOn这个序列中。
现在的话,中间序列在创建的时候就获取到了ConcurrentDispatchQueueScheduler类型的对象,ConcurrentDispatchQueueScheduler的对象保存了属性configuration,类型为DispatchQueue结构体,DispatchQueue结构体中保存了我们创建好的DispatchQueue队列。
现在知道了参数,看看回头看我们的subscribe(on:)方法创建中间序列的时候做了什么。在第一张图中看到返回的是SubscribeOn类型的序列,所以直接来到SubscribeOn中
这里是不是很熟悉,继承自pruduce,保存source和调度者,和普通的序列做的几乎相同的事情。
那么订阅呢,我们都知道订阅其实最后都是会走到SubscribeOn的run方法,所以直接来看run方法就好了,先看sink
在这里看到,其实是同样的逻辑,保存属性,然后走自己的父类sink的构造方法。这里需要注意的是SubscribeOnSink 拥有ObservableType和Observer两个协议,说明SubscribeOnSink是一个序列同时也是一个订阅者,这是一个很重要的概念。
再接着上一步sink之后的subscription,看到是调用了SubscribeOnSink的run方法。同样快速的来到SubscribeOnSink的run方法
这里和之前不同了哦,在这打个断点看看。
这里可以很明确的看到就是这一句在执行我们的发布订阅,想想我们的第二个问题,在这里是不是看它的实现就可以知道了。所以准备好了吗。
嗯?可是去哪里找呢,来看下图
先看两个箭头指的地方,observable 和匿名类 AnonymousObserver,这里是不是也同样证明了SubscribeOnSink是一个序列同时也是一个订阅者。
lldb调试之后翻译self.parent.source.subscribe(self)这一句
SubscribeOnSink.SubscribeOn.ObservableSequence.subscribe(SubscribeOnSink)
所以去看ObservableSequence的subscribe方法就好
看到继承自producer,其实这里我们就可以去看run方法了。因为明确的知道ObservableSequence没有重写subscribe方法的话,那必然还是会回到ObservableSequence的run方法,这里其实需要关注的是elements和scheduler这两个属性。
我们看到在创建sink的时候将ObservableSequence传入,那就说明ObservableSequenceSink是拥有所有的发布订阅和一个调度者对象的。再来看看ObservableSequenceSink的run方法做了什么
这里看其实还是有一点不明确的,但是这里有看出一点端倪,就是iterator,这是迭代器的意思,然后recurse递归,根据方法名其实能猜到个大概,就是在next的时候,根据迭代器递归的去发布订阅。
那么这里的sceduler又是谁呢,这里其实就是我们在创建ObservableSequence传入的CurrentThreadScheduler。
再来看内部的实现scheduleRecursive方法,scheduleRecursive是来自ImmediateSchedulerType协议的扩展
这里是创建RecursiveImmediateScheduler 的类,根据传入的参数state调用schedule方法,state也是ObservableSequence 保存的属性elements。等等,这里准确的来说是通过Sequence类型的makeIterator()方法获得自己的迭代器的属性。而action就是我们逃逸闭包中的代码。
那么很明确,在这里是调用schedule方法完成的实现,再进一步
重点的就一句代码,如果action存在就递归的调用action方法,而action呢,就是我们传入的逃逸闭包,这里action的创建也很有意思,在加锁的情况下根据迭代器来一个一个的创建逃逸闭包,所以这里也就是我们问题二的答案,通过加锁来控制迭代器创建发布订阅的逃逸闭包,这样达到一个顺序的目的。
调试的时候也可以明确的看到这一点。
总结
RxSwift的并发序列是:
1.先创建一个带有队列参数的中间序列。
2.中间序列既可以作为序列被订阅,也可以订阅源序列。
3.通过递归的方式在逃逸闭包中发布订阅
4.通过加锁和迭代器的方式来控制逃逸闭包的创建,实现顺序的输出源序列订阅的发布。