iOS的幾種定時(shí)器(Timer)創(chuàng)建方式

開(kāi)發(fā)過(guò)程中韩容,遇到在某個(gè)時(shí)間或按照某個(gè)周期來(lái)執(zhí)行一些方法的時(shí)候齐婴,就會(huì)用到定時(shí)器单匣。下面就是幾種開(kāi)發(fā)中常見(jiàn)的定時(shí)器的創(chuàng)建方式蚁阳。

1.Timer(NSTimer)

(1) target action:

    func testTimer(){
        let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
        // 需要手動(dòng)加入到runLoop中
        // 如果想不受scrollView頁(yè)面滑動(dòng)影響(滑動(dòng)時(shí)不響應(yīng)selector铃绒,滑動(dòng)停止后恢復(fù)),需設(shè)置當(dāng)前runloop的commonMode模式
        RunLoop.current.add(timer, forMode: .default)
    }

    @objc func timerAction(){
        print("timerAction")
    }

(2) scheduledTimer block

// 默認(rèn)添加到runLoop中螺捐,使用defaultMode模式
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (timer) in
            print("timerAction")
        })
// 可以手動(dòng)設(shè)置當(dāng)前runloop的commonMode模式避免頁(yè)面滑動(dòng)影響
RunLoop.current.add(timer, forMode: .common)

NSTimer的執(zhí)行依賴runLoop颠悬,如果當(dāng)前runLoop正在執(zhí)行一個(gè)連續(xù)性的運(yùn)算,timer就會(huì)被延時(shí)觸發(fā)定血。當(dāng)延遲超過(guò)timer的一個(gè)重復(fù)周期赔癌,會(huì)在延時(shí)結(jié)束后按照指定的周期繼續(xù)執(zhí)行。

2.GCD(DispatchSourceTimer)

let gcdTimer = DispatchSource.makeTimerSource()
gcdTimer.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(1))
gcdTimer.setEventHandler(handler: {
    print("GCD timerAction")
})
// 默認(rèn)掛起狀態(tài)澜沟,需手動(dòng)啟動(dòng)
gcdTimer.resume()
// 掛起灾票,掛起狀態(tài)時(shí)不能釋放(置為nil),會(huì)導(dǎo)致崩潰
// 可以再次resume()喚起定時(shí)器
gcdTimer.suspend()
// 取消茫虽,解釋定時(shí)任務(wù)
// 取消任務(wù)后如果想再次執(zhí)行Timer刊苍,只能重新創(chuàng)建一個(gè) Timer
gcdTimer.cancel()
gcdTimer = nil

GCD創(chuàng)建的Timer不受runLoop影響既们,使用DispatchSource,可以實(shí)現(xiàn)更加精準(zhǔn)的定時(shí)效果正什。

3.CADisplayLink

let cadTimer = CADisplayLink(target: self, selector: #selector(timerAction))
// 單位是幀啥纸,屏幕刷新多少幀時(shí)調(diào)用一次selector
// 默認(rèn)值為0,選擇使用設(shè)備的最高屏幕刷新頻率(iOS為60次每秒)
// 所以執(zhí)行時(shí)間間隔為:preferredFramesPerSecond * 最高屏幕刷新間隔婴氮,如iOS設(shè)備:preferredFramesPerSecond * 1/60 秒
cadTimer.preferredFramesPerSecond = 20
// 注冊(cè)到runLoop中監(jiān)聽(tīng)
cadTimer.add(to: RunLoop.current, forMode: .default)
// 掛起
// cadTimer.isPaused = true

// 終止
cadTimer?.invalidate()
cadTimer = nil

由于跟屏幕刷新同步脾拆,非常適合UI的重復(fù)繪制,如:下載進(jìn)度條莹妒,自定義動(dòng)畫設(shè)計(jì),視頻播放渲染等绰上。

4.RxSwift(interval/timer)

// interval方式
// period:每次發(fā)送的時(shí)間間隔旨怠,scheduler:調(diào)度者
timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)  
// timer方式
//timer = Observable<Int>.timer(1, scheduler: MainScheduler.instance)
timer?.subscribe(onNext: { (time) in
        print(time)
})
.disposed(by: disposeBag)

源碼解析:
interval,timer創(chuàng)建都是使用以下初始化方式蜈块。

    public static func timer(_ dueTime: RxTimeInterval, period: RxTimeInterval? = nil, scheduler: SchedulerType)
        -> Observable<Element> {
        return Timer(
            dueTime: dueTime,
            period: period,
            scheduler: scheduler
        )
    }
final private class Timer<Element: RxAbstractInteger>: Producer<Element> {
    fileprivate let _scheduler: SchedulerType
    fileprivate let _dueTime: RxTimeInterval
    fileprivate let _period: RxTimeInterval?

    init(dueTime: RxTimeInterval, period: RxTimeInterval?, scheduler: SchedulerType) {
        self._scheduler = scheduler
        self._dueTime = dueTime
        self._period = period
    }

    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 {
            let sink = TimerOneOffSink(parent: self, observer: observer, cancel: cancel)
            let subscription = sink.run()
            return (sink: sink, subscription: subscription)
        }
    }
}

了解過(guò)RxSwift的同學(xué)鉴腻,Timer類是不是很眼熟,是不是和AnonymousObservable非常相似百揭,一樣的繼承Producer爽哎,一樣的init方式,一樣的run方法中創(chuàng)建sink器一。有興趣的同學(xué)可以看看這兩篇RxSwift(二)原理-執(zhí)行流程
课锌,RxSwift(三)原理深入探究

原理和RxSwift核心原理差不多祈秕,只是生成了TimerSink或者TimerOneOffSink渺贤,sink調(diào)用run方法,以TimerSink為例:
跟一下源碼:sink.run()

final private class TimerSink<Observer: ObserverType> : Sink<Observer> where Observer.Element : RxAbstractInteger  {
    func run() -> Disposable {
        return self._parent._scheduler.schedulePeriodic(0 as Observer.Element, startAfter: self._parent._dueTime, period: self._parent._period!) { state in
            self._lock.lock(); defer { self._lock.unlock() }
            self.forwardOn(.next(state))
            return state &+ 1
        }
    }
}
public class SerialDispatchQueueScheduler : SchedulerType {
/**
    Schedules a periodic piece of work.
    
    - parameter state: State passed to the action to be executed.
    - parameter startAfter: Period after which initial work should be run.
    - parameter period: Period for running the work periodically.
    - parameter action: Action to be executed.
    - returns: The disposable object used to cancel the scheduled action (best effort).
    */
    public func schedulePeriodic<StateType>(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable {
        return self.configuration.schedulePeriodic(state, startAfter: startAfter, period: period, action: action)
    }
}
extension DispatchQueueConfiguration {
func schedulePeriodic<StateType>(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable {
        let initial = DispatchTime.now() + startAfter

        var timerState = state

        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
    }
}

可以看到這里其實(shí)就是初始化了一個(gè)DCD Timer请毛,這里有一句關(guān)鍵代碼:timerState = action(timerState)志鞍。
這里的action就是schedulePeriodic傳入的閉包:

        { state in
            self._lock.lock(); defer { self._lock.unlock() }
            self.forwardOn(.next(state))
            return state &+ 1
        }

閉包里通過(guò)state的遞增,重復(fù)調(diào)用self.forwardOn(.next(state))方仿,這句代碼會(huì)調(diào)用self._observer.on(event)固棚,將event傳入到subscribe()訂閱方法中,訂閱方法創(chuàng)建匿名觀察者AnonymousObserver保存的閉包會(huì)根據(jù)傳入的event枚舉值對(duì)應(yīng)到onNext()仙蚜。

RxSwift的timer是封裝的DispatchSource定時(shí)器的此洲,所以也是不受runloop影響的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鳍征,一起剝皮案震驚了整個(gè)濱河市黍翎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艳丛,老刑警劉巖匣掸,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趟紊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡碰酝,警方通過(guò)查閱死者的電腦和手機(jī)霎匈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)送爸,“玉大人铛嘱,你說(shuō)我怎么就攤上這事∠В” “怎么了墨吓?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纹磺。 經(jīng)常有香客問(wèn)我帖烘,道長(zhǎng),這世上最難降的妖魔是什么橄杨? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任秘症,我火速辦了婚禮,結(jié)果婚禮上式矫,老公的妹妹穿的比我還像新娘乡摹。我一直安慰自己,他們只是感情好采转,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布聪廉。 她就那樣靜靜地躺著,像睡著了一般故慈。 火紅的嫁衣襯著肌膚如雪锄列。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天惯悠,我揣著相機(jī)與錄音邻邮,去河邊找鬼。 笑死克婶,一個(gè)胖子當(dāng)著我的面吹牛筒严,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播情萤,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鸭蛙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了筋岛?” 一聲冷哼從身側(cè)響起娶视,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后肪获,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寝凌,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年孝赫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了较木。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡青柄,死狀恐怖伐债,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情致开,我是刑警寧澤峰锁,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站双戳,受9級(jí)特大地震影響祖今,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拣技,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耍目。 院中可真熱鬧膏斤,春花似錦、人聲如沸邪驮。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毅访。三九已至沮榜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喻粹,已是汗流浹背蟆融。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留守呜,地道東北人型酥。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像查乒,于是被迫代替她去往敵國(guó)和親弥喉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容