RxSwift之定时器(timer)

3,682 阅读3分钟

三种常用的timer

1.第一种Timer(NSTimer) 第一步:初始化timer

let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)

第二步:实现回调函数

@objc func timerAction() {
        
    print("timer action!")
}

第三步:启动定时器

let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)

timer.fire()

执行之后会发现,timerAction() 函数只执行了一次。这是因为Timer定时器,需要和RunLoop支持。所以,需要把第三步的代码修改成如下。

let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)

RunLoop.current.add(timer, forMode: .default)

在iOS10.0以后,Timer增加了block回调,但是用法都是一样的,就不再举例说明了。

2.第二种GCD方式(DispatchSourceTimer)

let timer = DispatchSource.makeTimerSource()

timer.setEventHandler {
    print(timer)
} 

timer.schedule(deadline: DispatchTime.now(), repeating: 1)

timer.resume()

DispatchSourceTimer 需要设置deadline。同时,需要执行 resume() 函数,因为 DispatchSourceTimer 默认是挂起的,需要resume才会开启定时器。

2.第三种CADisplayLink

let timer = CADisplayLink(target: self, selector: #selector(timerAction))
        
timer.preferredFramesPerSecond = 1
        
timer.add(to: .current, forMode: .common)

CADisplayLink 需要设置preferredFramesPerSecond屏幕在一分钟内刷新的帧数,默认为60,如果不设置,则一秒执行60次。 同时需要将创建的 CADisplayLink 对象加入到 RunLoop 中,才能在屏幕刷新的时候,执行定时器的 selector 方法。

RxSwift中的定时器(timer)

let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
    
timer.subscribe(onNext: { (time) in
    print(time)
})
    .disposed(by: disposeBag)

从上面的示例中可以看出,RxSwift中的timer是一个可观察序列,而且也没有RunLoop的支持。那么,RxSwift的定时器timer内部是如何实现的呢?我们一起来看看源码。

通过源码可以知道,interval 内部是初始化了一个 Timer 类的实例对象。 我们再查看 Timer 的源码。
可以知道 Timer 类是 Producer 的子类。我在RxSwift核心逻辑简介 中已经分析过了,在订阅信号调用 subscribe() 函数之后,会调用 run() 函数。

最后会初始化 TimerSink 实例对象,并调用其 run() 函数。

再继续往下查看源码会发现有很多类都实现了schedulePeriodic()函数,但是我们应该进入哪个文件查看呢?因为我们初始化timer时传入的参数是MainScheduler的实例,所以我们应该找和MainScheduler相关的类的实现
然后一直找下去就会找到 schedulePeriodic 函数

    func schedulePeriodic<StateType>(_ state: StateType, startAfter: TimeInterval, period: TimeInterval, action: @escaping (StateType) -> StateType) -> Disposable {
        let initial = DispatchTime.now() + dispatchInterval(startAfter)

        var timerState = state

        let timer = DispatchSource.makeTimerSource(queue: self.queue)
        timer.schedule(deadline: initial, repeating: dispatchInterval(period), 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 cancelTimer.isDisposed {
                return
            }
            timerState = action(timerState)
        })
        timer.resume()
        
        return cancelTimer
    }

schedulePeriodic 函数中就会发现,RxSwift中timer的底层实现,其实是一个 DispatchSourceTimer

并且在每次调用 timerEventHandler 时会执行 action(timerState) 闭包。通过分析可以知道,闭包 action

这一个代码块。最终timer每次都会执行 self.forwardOn(.next(state))

这之后的代码执行逻辑,就和RxSwift之管道——AnonymousObservableSink4、observer.onNext()分析的逻辑一致,就不再过多赘述了。

以上,就是RxSwift的定时器底层逻辑分析。如有不足之处,还请大家在评论中指出,不胜感激。