RxSwift中的定时器

RxSwift是一个比较大的框架,如果我们项目使用了这个框架的话,可以尽量去使用里面封装的一些功能,比如定时器Timer
首先说明源码中提供的定时器方法有2个:

// 开启一个定时器:在dueTime后,每隔period在scheduler中发送一次onNext:
// 这里的period如果为空,则是一个一次性的timer,触发一次之后就结束
public static func timer(_ dueTime: RxSwift.RxTimeInterval, period: RxSwift.RxTimeInterval? = nil, scheduler: RxSwift.SchedulerType) -> RxSwift.Observable<Self.Element>

// 本方法是上面方法中dueTime = period的情况
public static func interval(_ period: RxSwift.RxTimeInterval, scheduler: RxSwift.SchedulerType) -> RxSwift.Observable<Self.Element>

因为上述两个方法的区别较小,所以这里以interval方法为例:

一、使用方法

开启定时器一般分为在主线程、子线程两种情况:
RxSwift中的Scheduler

var timerDispose: Disposable?
/// 1. 在主线程开启定时器,并且在主线程回调onNext:
/// 定时器销毁的时机在:信号执行dispose之后(disposeBag销毁、onError、onComplete)
timerDispose = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .subscribe(onNext: { num in
        print(Thread.current, "subscribe===>", num)
    })
    timerDispose.disposed(by: self.disposeBag) // 在自身销毁时销毁定时器
// timerDispose.dispose() // 手动销毁定时器的方法

/// 2. 在子线程开启定时器,并且在子线程回调onNext:
timerDispose = Observable<Int>.interval(.seconds(2), scheduler: SerialDispatchQueueScheduler(internalSerialQueueName: "testTimer"))
    .subscribe(onNext: { num in // num会调用一次加1
        print(Thread.current, "===>", num)
        DispatchQueue.main.async {
            print("testTimer:回到主线程做事", Thread.current)
        }
    })
    timerDispose.disposed(by: self.disposeBag) // 在自身销毁时销毁定时器
// timerDispose.dispose() // 手动销毁定时器的方法
二、RxSwift中定时器的大体过程
  1. 创建RxSwift中的Timer对象并返回
public static func interval(_ period: RxTimeInterval, scheduler: SchedulerType)
    -> Observable<Element> {
    return Timer(dueTime: period, period: period,  scheduler: scheduler  )
}

--》2. 在Timer被订阅(subscribe)时,首先会创建一个AnonymousObserver类型的匿名observer, 返回一个Disposable, 里面的self.asObservable().subscribe(observer),中self是Timer实例,继承自Producer类,所以会执行Producer类的subscribe方法。

public func subscribe( onNext: ((Element) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil
) -> Disposable {
        let disposable: disposable = Disposables.create()
        let observer = AnonymousObserver<Element> { event in
            switch event {
            case .next(let value):
                onNext?(value)
            case .error(let error):// 省略
            case .completed:// 省略
            }
        }
        return Disposables.create(
            self.asObservable().subscribe(observer), // 这里的self是Timer
            disposable
        )
}

--》3. 接下来会执行Timer的父类Producersubscribe方法,然后执行Timerrun方法。如下代码中CurrentThreadScheduler.instance.schedule函数内部主要逻辑是调用这个尾随闭包,闭包中会调用self.run(observer, cancel: disposer), 这里的self还是Timer。

/// Producer中subscribe
override func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
     return CurrentThreadScheduler.instance.schedule(()) { _ in
            let disposer = SinkDisposer()
            let sinkAndSubscription = self.run(observer, cancel: disposer)
            disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)

            return disposer
        }
}

/// 这里是Timer的run方法
override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
    if self.period != nil {
        let sink = TimerSink(parent: self, observer: observer, cancel: cancel)
        let subscription = sink.run()
        return (sink: sink, subscription: subscription)
    }
    else {// 一次性的定时器,这里略 }
}

--》 4. 接下来是TimerSink的实例调用sink.run(), 可以看到内部是执行scheduler的schedulePeriodic方法,方法最后一个参数action是尾随闭包,这里的scheduler是我们创建定时器时创建的,因为MainScheduler是继承SerialDispatchQueueScheduler的,所以这里以后者来追踪。可以看到会转给scheduler内部的configuration执行schedulePeriodic方法

func run() -> Disposable {
    return self.parent.scheduler.schedulePeriodic(0 as Observer.Element, startAfter: self.parent.dueTime, period: self.parent.period!) { state in
        self.lock.performLocked {
            self.forwardOn(.next(state))
            return state &+ 1 // 调用一次之后让state+1,这样在订阅next里面的num会每次加1
        }
    }
}

/// SerialDispatchQueueScheduler中的schedulePeriodic方法,会转给
public func schedulePeriodic<StateType>(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable {
    self.configuration.schedulePeriodic(state, startAfter: startAfter, period: period, action: action)
}

--》5. scheduler内部的configuration执行schedulePeriodic方法的逻辑, 可以看到内部是在self.queue中创建了一个TimerSource, 定时器每次触发时,会执行timerState = action(timerState)即第4步中传进来的action, 可以看到是会执行self.forwardOn(.next(state)), 即发送一个next事件 --> 最终使用者收到onNext调用.

func schedulePeriodic<StateType>(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable {
    let initial = DispatchTime.now() + startAfter
    var timerState = state
// self.queue是在scheduler创建时内部创建的,所以是不是主线程取决于创建scheduler的类型
    let timer = DispatchSource.makeTimerSource(queue: self.queue)
    timer.schedule(deadline: initial, repeating: period, leeway: self.leeway)
    var timerReference: DispatchSourceTimer? = timer
    let cancelTimer = Disposables.create {
        timerReference?.cancel()
        timerReference = nil
    }

    timer.setEventHandler(handler: {
        if cancelTimer.isDisposed {
            return
        }
        timerState = action(timerState)
    })
    timer.resume()
    
    return cancelTimer
}

--》 6. 我们在使用时如果对时间准确度要求比较高的可以使用MainScheduler.instance; 如果对时间准确度要求高的可以使用SerialDispatchQueueScheduler(internalSerialQueueName: "testTimer")

三、 销毁定时器

销毁定时器的方法:
使用定时器时引用返回的Disposable,在不需要定时器时执行dispose即可
原理:
可以看到在上述第2步中,创建匿名observer时会返回Disposable, 内部是引用了匿名observer的,在Disposable执行dispose时,observer也会失去引用而销毁; 然后在上述的第5步中可以看到cancelTimer在执行dispose时,会执行timerReference?.cancel() ; timerReference = nil

四、 使用系统定时器

系统定时器:
1、系统封装的Timer,使用起来跟NSTimer是一样的。
2、系统的GCDTiemr,使用方法跟第5步是一样的, 缺少了当前是第几次触发的功能。

我更倾向于使用RxSwift中定时器是因为以下几个原因:

  1. 使用也很简单方便
  2. 能够知道当前是第几次定时触发
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章