RxSwift的Scheduler是怎样实现的

1,953 阅读8分钟

前言

前面整理了RxSwift的subscribeOn与observeOn源码。本文将重点总结RxSwift的Scheduler,以日常开发中使用频率较高的ConcurrentDispatchQueueSchedulerMainScheduler为主。前文也提到过,subscribeOn与observeOn只是基于开发者设置的scheduler在流程上进行线程调度的控制。这里可以理解为相对于subscribeOn与observeOn,scheduler只是它们的工具。(ps:本文基于RxSwift 5.1.1)

Rx的Observable.create干了啥?

RxSwift的subscribeOn与observeOn源码解析

ImmediateSchedulerType&SchedulerType

ImmediateSchedulerTypeSchedulerType两个协议为Scheduler类指定了规则。SchedulerType继承自ImmediateSchedulerType。在RxSwift中,各Scheduler类大部分继承自SchedulerType协议,也有只继承ImmediateSchedulerType的。

SchedulerType协议主要在扩展了两个定时触发的方法scheduleRelativeschedulePeriodic

// ImmediateSchedulerType.swift
public protocol ImmediateSchedulerType {
    func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable
}

// SchedulerType.swift
public protocol SchedulerType: ImmediateSchedulerType {
    /// - returns: Current time.
    var now : RxTime {
        get
    }

    func scheduleRelative<StateType>(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable

    func schedulePeriodic<StateType>(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable
}

源码:

ConcurrentDispatchQueueScheduler

ConcurrentDispatchQueueScheduler的创建可直接传入一个qos,即我们熟悉的GCD中DispatchQoS。源码中会帮我们创建一个DispatchQueue,属性设置为并发,即ConcurrentDispatchQueueScheduler内管理着一个GCD并发队列。

当然,这里的构造方法还可以直接传入一个DispatchQueue。这意味着我们也可以传一个串行队列进去(虽然这种做法不太合理,哈哈...)。

通过构造方法,我们也能看到,DispatchQueue最后是被托管到一个DispatchQueueConfiguration中了。DispatchQueueConfiguration才是真正调用DispatchQueue进行线程调度的地方

// ConcurrentDispatchQueueScheduler.swift
public class SerialDispatchQueueScheduler : SchedulerType

ConcurrentDispatchQueueScheduler.init(qos: .userInteractive)

@available(iOS 8, OSX 10.10, *)
public convenience init(qos: DispatchQoS, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) {
    self.init(queue: DispatchQueue(
            label: "rxswift.queue.\(qos)",
            qos: qos,
            attributes: [DispatchQueue.Attributes.concurrent],
            target: nil),
            leeway: leeway)
}

public init(queue: DispatchQueue, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) {
    self.configuration = DispatchQueueConfiguration(queue: queue, leeway: leeway)
}

源码:

DispatchQueueConfiguration

结构体DispatchQueueConfiguration属性如下,关于DispatchQueue的调用定义在它的扩展代码中,这个我们后面会讲到。

struct DispatchQueueConfiguration {
    let queue: DispatchQueue
    let leeway: DispatchTimeInterval
}

这里先让我们会想一下,在讲订阅的时候,代码最终会走到SubscribeOnSink.run()方法。这里会触发scheduler的线程调度

// SubscribeOn.swift - class SubscribeOnSink
func run() -> Disposable {
        let disposeEverything = SerialDisposable()
        let cancelSchedule = SingleAssignmentDisposable()
        
        disposeEverything.disposable = cancelSchedule
        
        let disposeSchedule = self.parent.scheduler.schedule(()) { _ -> Disposable in
            let subscription = self.parent.source.subscribe(self)
            disposeEverything.disposable = ScheduledDisposable(scheduler: self.parent.scheduler, disposable: subscription)
            return Disposables.create()
        }

        cancelSchedule.setDisposable(disposeSchedule)
    
        return disposeEverything
}

假如我们设置的scheduler为ConcurrentDispatchQueueScheduler,则它的schedule方法为如下代码。内部调用的是DispatchQueueConfiguration.schedule方法。最终会将传入的闭包抛到队列异步执行

可以看到,ConcurrentDispatchQueueScheduler的多线程逻辑最终依赖的是GCD

// ConcurrentDispatchQueueScheduler.swift
public final func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        return self.configuration.schedule(state, action: action)
}

// DispatchQueueConfiguration.swift
func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        let cancel = SingleAssignmentDisposable()

        self.queue.async {
            if cancel.isDisposed {
                return
            }

            cancel.setDisposable(action(state))
        }

        return cancel
}

这里顺带讲一下scheduleRelative方法,这是一个定时触发的线程调度(还有一个schedulePeriodic方法是可以执行重复触发的逻辑,可类比该方法)。从DispatchQueueConfiguration的源码中可以看到,这里利用的是GCD的DispatchSourceTimer进行定时。

比较有意思的是,这里的DispatchSourceTimer不需要全局持有也能正常使用,作者也加了TODO注释。笔者特意去查看了最新版的RxSwift,并没有看到代码的更新。如果有知道的朋友可以下方讨论一下。

// ConcurrentDispatchQueueScheduler.swift
public final func scheduleRelative<StateType>(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable {
        return self.configuration.scheduleRelative(state, dueTime: dueTime, action: action)
}

// DispatchQueueConfiguration.swift
func scheduleRelative<StateType>(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable {
        let deadline = DispatchTime.now() + dueTime

        let compositeDisposable = CompositeDisposable()

        let timer = DispatchSource.makeTimerSource(queue: self.queue)
        timer.schedule(deadline: deadline, leeway: self.leeway)

        // TODO:
        // This looks horrible, and yes, it is.
        // It looks like Apple has made a conceputal change here, and I'm unsure why.
        // Need more info on this.
        // It looks like just setting timer to fire and not holding a reference to it
        // until deadline causes timer cancellation.
        var timerReference: DispatchSourceTimer? = timer
        let cancelTimer = Disposables.create {
            timerReference?.cancel()
            timerReference = nil
        }

        timer.setEventHandler(handler: {
            if compositeDisposable.isDisposed {
                return
            }
            _ = compositeDisposable.insert(action(state))
            cancelTimer.dispose()
        })
        timer.resume()

        _ = compositeDisposable.insert(cancelTimer)

        return compositeDisposable
    }

源码:

SerialDispatchQueueScheduler

在讲MainScheduler之前,先来看看SerialDispatchQueueScheduler因为MainScheduler继承自SerialDispatchQueueScheduler。和ConcurrentDispatchQueueScheduler逻辑类似,只不过它主要是作为串行队列的逻辑封装

// MainScheduler.swift
public final class MainScheduler : SerialDispatchQueueScheduler

因为都是继承自SchedulerType,所以SerialDispatchQueueSchedulerConcurrentDispatchQueueScheduler逻辑代替相同,这里就不过多赘述了。DispatchQueue队列也是托管给DispatchQueueConfiguration管理的,逻辑与上述相同

// SerialDispatchQueueScheduler.swift
public class SerialDispatchQueueScheduler : SchedulerType

init(serialQueue: DispatchQueue, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) {
        self.configuration = DispatchQueueConfiguration(queue: serialQueue, leeway: leeway)
}

区别较大的是SerialDispatchQueueScheduler.schedule方法被改为调用自身的scheduleInternal方法了默认情况下,还是会调用DispatchQueueConfiguration.schedule方法。根据上述介绍的,默认就会将传入的闭包抛到队列异步执行

这里预告一下,MainScheduler就会重写scheduleInternal来适配主线程的逻辑。

// SerialDispatchQueueScheduler.swift 
public final func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        return self.scheduleInternal(state, action: action)
}

func scheduleInternal<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        return self.configuration.schedule(state, action: action)
}

源码:

MainScheduler

上述提到,MainScheduler继承自SerialDispatchQueueScheduler,因为主线程为单线程,这里可以理解MainScheduler为一个特殊的SerialDispatchQueueScheduler,即特殊的串行队列

下述代码中,MainScheduler拥有一个成员变量_mainQueue,即DispatchQueue.mainnumberEnqueued是用于处理多线程并发的,这个后面会讲到。

// MainScheduler.swift
public final class MainScheduler : SerialDispatchQueueScheduler {

    private let _mainQueue: DispatchQueue

    let numberEnqueued = AtomicInt(0)

    /// Initializes new instance of `MainScheduler`.
    public init() {
        self._mainQueue = DispatchQueue.main
        super.init(serialQueue: self._mainQueue)
    }
    ...

值得一提的是,MainScheduler是一个单例,并且提供了两个实体共我们使用。

  • instance即使用MainScheduler本身。
  • asyncInstance即利用DispatchQueue.main创建一个SerialDispatchQueueScheduler对象。后续的逻辑就与上述SerialDispatchQueueScheduler的介绍相同了
// MainScheduler.swift
public static let instance = MainScheduler()

public static let asyncInstance = SerialDispatchQueueScheduler(serialQueue: DispatchQueue.main)

MainScheduler中重写了scheduleInternal方法。最终的结果是将传入的闭包抛到主线程异步执行。当然,若当前为主线程且没有正在执行的逻辑(previousNumberEnqueued == 0),会采用同步执行的方式

在这里有一个numberEnqueued属性+1、-1的逻辑。上文可知,numberEnqueued是一个原子属性的Int型(AtomicInt,RxSwift自己封装的线程安全Int型)。在执行闭包前+1,执行闭包后-1。此操作笔者个人理解是在多线程并发调用的情况下,保证事件的顺序不被修改,而又能够在主线程空闲的情况下同步执行。这里的+1、-1也有一点像递归锁的思想

// MainScheduler.swift
override func scheduleInternal<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        let previousNumberEnqueued = increment(self.numberEnqueued)

        if DispatchQueue.isMain && previousNumberEnqueued == 0 {
            let disposable = action(state)
            decrement(self.numberEnqueued)
            return disposable
        }

        let cancel = SingleAssignmentDisposable()

        self._mainQueue.async {
            if !cancel.isDisposed {
                _ = action(state)
            }

            decrement(self.numberEnqueued)
        }

        return cancel
}

源码:

ConcurrentMainScheduler

在MainScheduler.swift的文件开头有这样一段注释:

This scheduler is optimized for observeOn operator. To ensure observable sequence is subscribed on main thread using subscribeOn operator please use ConcurrentMainScheduler because it is more optimized for that purpose.

大体意思是MainScheduler针对observeOn操作符进行了优化,而ConcurrentMainScheduler针对subscribeOn进行了优化。所以接下来,我们看看ConcurrentMainScheduler究竟是啥。

ConcurrentMainScheduler可以理解为是MainScheduler的一个装饰器,因为除了重写SchedulerType.schedule()方法,其他逻辑都是调用MainScheduler对应的方法

// ConcurrentMainScheduler.swift
public final class ConcurrentMainScheduler : SchedulerType {
    public typealias TimeInterval = Foundation.TimeInterval
    public typealias Time = Date

    private let _mainScheduler: MainScheduler
    private let _mainQueue: DispatchQueue

    /// - returns: Current time.
    public var now: Date {
        return self._mainScheduler.now as Date
    }

    private init(mainScheduler: MainScheduler) {
        self._mainQueue = DispatchQueue.main
        self._mainScheduler = mainScheduler
    }

    /// Singleton instance of `ConcurrentMainScheduler`
    public static let instance = ConcurrentMainScheduler(mainScheduler: MainScheduler.instance)
    ...

schedule方法中与MainScheduler.scheduleInternal的区别在于,少了原子属性作为线程安全的处理。结合上述作者推荐在subscribeOn操作符使用,笔者个人理解为订阅的过程不存在并发的情况,所以可以忽略线程安全的问题

// ConcurrentMainScheduler.swift
public func schedule<StateType>(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable {
        if DispatchQueue.isMain {
            return action(state)
        }

        let cancel = SingleAssignmentDisposable()

        self._mainQueue.async {
            if cancel.isDisposed {
                return
            }

            cancel.setDisposable(action(state))
        }

        return cancel
}

源码:

RecursiveImmediateScheduler

RecursiveImmediateScheduler不是一个Scheduler,这里我们先简单回顾一下observeOn

在创建observeOn时,代码会根据传入的scheduler是否为SerialDispatchQueueScheduler来创建ObserveOnSerialDispatchQueue对象或ObserveOn对象

// ObserveOn.swift - extension ObservableType
public func observeOn(_ scheduler: ImmediateSchedulerType) -> Observable<Element> {
     if let scheduler = scheduler as? SerialDispatchQueueScheduler {
         return ObserveOnSerialDispatchQueue(source: self.asObservable(), scheduler: scheduler)
     }
     else {
         return ObserveOn(source: self.asObservable(), scheduler: scheduler)
     }
}
  • 若为ObserveOnSerialDispatchQueue,最终会在事件传递时触发ObserveOnSerialDispatchQueueSink.onCore方法,对应的就是我们上述所讲的schedule方法
// ObserveOn.swift - class ObserveOnSerialDispatchQueueSink
override func onCore(_ event: Event<Element>) {
     _ = self.scheduler.schedule((self, event), action: self.cachedScheduleLambda!)
}
  • 若为ObserveOn,最终会在事件传递时触发ObserveOnSink.onCore方法,这里调用了一个我们没见过的方法scheduleRecursive递归的线程调度
// ObserveOn.swift - class ObserveOnSink
override func onCore(_ event: Event<Element>) {
     let shouldStart = self._lock.calculateLocked { () -> Bool in
         self._queue.enqueue(event)

         switch self._state {
         case .stopped:
             self._state = .running
             return true
         case .running:
             return false
         }
     }

     if shouldStart {
         self._scheduleDisposable.disposable = self._scheduler.scheduleRecursive((), action: self.run)
     }
}

scheduleRecursive来源于ImmediateSchedulerType的扩展。这里会根据需要执行的闭包以及scheduler对象创建一个RecursiveImmediateScheduler对象。这里也可以理解是RecursiveImmediateScheduler为我们设置的Scheduler对象的装饰器

// ImmediateSchedulerType.swift - extension ImmediateSchedulerType
public func scheduleRecursive<State>(_ state: State, action: @escaping (_ state: State, _ recurse: (State) -> Void) -> Void) -> Disposable {
    let recursiveScheduler = RecursiveImmediateScheduler(action: action, scheduler: self)
        
    recursiveScheduler.schedule(state)
        
    return Disposables.create(with: recursiveScheduler.dispose)
}

RecursiveImmediateScheduler.schedule方法简单来说就是调用我们设置的Scheduler.schedule方法进行线程切换,值得注意的是,执行的闭包中会以RecursiveImmediateScheduler.schedule方法作为闭包参数

// RecursiveScheduler.swift - class RecursiveImmediateScheduler
func schedule(_ state: State) {
        var scheduleState: ScheduleState = .initial

        let d = self._scheduler.schedule(state) { state -> Disposable in
            // best effort
            if self._group.isDisposed {
                return Disposables.create()
            }
            
            let action = self._lock.calculateLocked { () -> Action? in
                ...

                return self._action
            }
            
            if let action = action {
                action(state, self.schedule)
            }
            
            return Disposables.create()
        }
        
        ...
}

不妨再结合ObserveOnSink.onCore方法中调用scheduleRecursive时传入的run方法。方法的最后会再次调用闭包recurse即为上述提到的RecursiveImmediateScheduler.schedule方法。以此达到递归调用的效果,线程对应的时scheduler所对应的线程。递归的出口为下述的布尔型shouldContinue

// ObserveOn.swift - class ObserveOnSink
func run(_ state: (), _ recurse: (()) -> Void) {
        let (nextEvent, observer) = self._lock.calculateLocked { () -> (Event<Element>?, Observer) in
            if !self._queue.isEmpty {
                return (self._queue.dequeue(), self._observer)
            }
            else {
                self._state = .stopped
                return (nil, self._observer)
            }
        }

        if let nextEvent = nextEvent, !self._cancel.isDisposed {
            observer.on(nextEvent)
            if nextEvent.isStopEvent {
                self.dispose()
            }
        }
        else {
            return
        }

        let shouldContinue = self._shouldContinue_synchronized()

        if shouldContinue {
            recurse(())
        }
    }

这里引申出了一个问题:为什么ObserveOnSerialDispatchQueue的最后可以直接调用scheduler.schedule方法,而ObserveOn却需要将事件先缓存,然后递归调用呢?

  • 回想一下observeOn方法中需要判断scheduler是否为SerialDispatchQueueScheduler或其子类。这说明如果执行的DispatchQueue为串行队列,那就不存在线程并发的问题。故ObserveOnSerialDispatchQueue不需要这种缓存操作的处理。

  • 而若是创建ObserveOn则意味着设置的scheduler有线程并发的能力(譬如ConcurrentDispatchQueueScheduler)。利用队列集合先将事件缓存,后续通过递归的方式传递给下游,则相当于是一种并发的数据合流的效果,这样可以保证数据按其相对顺序往下传递

// ObserveOn.swift - extension ObservableType
public func observeOn(_ scheduler: ImmediateSchedulerType) -> Observable<Element> {
     if let scheduler = scheduler as? SerialDispatchQueueScheduler {
         return ObserveOnSerialDispatchQueue(source: self.asObservable(), scheduler: scheduler)
     }
     else {
         return ObserveOn(source: self.asObservable(), scheduler: scheduler)
     }
}

源码:

最后

本文重点解析了常用的ConcurrentDispatchQueueSchedulerMainScheduler及它们相关类的源码。因为里面用到的都是GCD相关的逻辑,代码的逻辑还是比较好理解的。

系列文章: