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. 能夠知道當前是第幾次定時觸發
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章