iOS 定時器耗電探究

iOS開發(fā)中的幾種定時器

iOS開發(fā)中定時器實現(xiàn)方式大致有三種,一種是Timer實現(xiàn),一種是通過GCD自己創(chuàng)建,另一種是CADisplayLink創(chuàng)建。

Timer使用簡單,需要注意的是Timerrunloop聯(lián)系緊密,在用scheduled方式創(chuàng)建之后,程序會自動將其添加到runloop中,不再需要Timer時,必須手動將其釋放。

CADisplayLink與屏幕刷新率同步,也就是每當(dāng)屏幕刷新一次就可以執(zhí)行一次綁定的事件(iPhone的刷新頻率為60HZ,正常情況下1s需要刷新60次),也可以通過其屬性設(shè)置間隔多少幀執(zhí)行一次,一般用于即時畫面渲染,啟動CADisplayLink定時器需要調(diào)用add(to: <#T##RunLoop#>, forMode: <#T##RunLoopMode#>)方法。

gcd定時器主要用于精準(zhǔn)定時,它的精度能達到納秒級別。gcd不同于上面兩種定時器,它不會受runloop的影響(runloop也是通過gcd實現(xiàn)),所以精確,例如Timer加入runloop默認(rèn)模式時,當(dāng)有滑動控件在滑動時會暫停計時,但是gcd不會。gcd可控性強,但是使用稍微復(fù)雜

定時器普通模式下不能在后臺運行,需要設(shè)置Background Modes,選擇一種可以后臺運行的模式

關(guān)于耗電測試

有時候開發(fā)需要多個任務(wù)定時執(zhí)行(與硬件結(jié)合開發(fā)較常見),可能他們的定時時間不一致,這就涉及到多個計時。

我試想了兩種實現(xiàn)方式,一種是通過創(chuàng)建多個定時器實現(xiàn),另一種是創(chuàng)建一個定時器,每個定時任務(wù)定義一個計數(shù)器,定時器每秒累加他們的計數(shù)器,當(dāng)滿足時間條件時就去執(zhí)行相應(yīng)的定時任務(wù),當(dāng)需要取消某個任務(wù)時就停止累加并將計數(shù)器置零。

比較一下這兩種方式,第一種簡單明了,便于維護,但是會存在多個定時器,消耗內(nèi)存資源。第二種只有一個定時器,但是代碼易讀性相對第一種較差,也會創(chuàng)建很多全局變量判斷是否需要累加計數(shù)器以及保存計數(shù)器的值。而且定時器執(zhí)行間隔是1s,執(zhí)行內(nèi)容是將所有任務(wù)計數(shù)器累加1,判斷每個任務(wù)的計數(shù)器值是否滿足執(zhí)行條件,耗費CPU資源。

雖然每個定時器都會消耗資源,但我個人還是認(rèn)為第一種方式較為科學(xué),耗電也不會比第二種高,因為第二種每秒都會執(zhí)行任務(wù)消耗CPU,比如一個10秒的定時任務(wù),第一種方式執(zhí)行1次,第二種方式需要執(zhí)行10次,也不夠靈活,管理多個任務(wù)的時候更難操控。接下來我需要驗證一下這個想法,我準(zhǔn)備先用Timer實現(xiàn)驗證。最后附帶gcd實現(xiàn)定時器的方式。

本次驗證耗電采用InstrumentEnergy Log工具,測試流程如下:

  • 1.設(shè)置app不鎖屏:UIApplication.shared.isIdleTimerDisabled = true

  • 2.安裝APP后斷開iPhone電源線

  • 3.退出所有運行的APP

  • 4.打開系統(tǒng)設(shè)置-開發(fā)者-logging開啟Energy后點擊Start Recording

  • 5.開啟需要測試的APP運行10分鐘

  • 6.打開系統(tǒng)設(shè)置-開發(fā)者-logging點擊Stop Recording

  • 7.打開InstrumentEnergy Log工具,從設(shè)備導(dǎo)出日志

8.修改方式后重新執(zhí)行以上步驟

驗證第一種實現(xiàn)方式

定義6個定時器控制6個任務(wù),每個任務(wù)時間不一致。
為了有操作消耗CPU資源,這里任務(wù)都是計算0-10000之間的完全數(shù)

完全數(shù)(Perfect number),又稱完美數(shù)或完備數(shù),是一些特殊的自然數(shù)。它所有的真因子(即除了自身以外的約數(shù))的和(即因子函數(shù)),恰好等于它本身。如果一個數(shù)恰好等于它的因子之和,則稱該數(shù)為“完全數(shù)”。

import UIKit

class ViewController: UIViewController {
    
    fileprivate var timer1: Timer?
    fileprivate var timer2: Timer?
    fileprivate var timer3: Timer?
    fileprivate var timer4: Timer?
    fileprivate var timer5: Timer?
    fileprivate var timer6: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        UIApplication.shared.isIdleTimerDisabled = true
        
        lotsTimer()
    }

    func lotsTimer(){
        
        timer1 = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(timer1Event), userInfo: nil, repeats: true)
        
        timer2 = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(timer2Event), userInfo: nil, repeats: true)
        
        timer3 = Timer.scheduledTimer(timeInterval: 40, target: self, selector: #selector(timer3Event), userInfo: nil, repeats: true)
        
        timer4 = Timer.scheduledTimer(timeInterval: 50, target: self, selector: #selector(timer4Event), userInfo: nil, repeats: true)
        
        timer5 = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(timer5Event), userInfo: nil, repeats: true)
        
        timer6 = Timer.scheduledTimer(timeInterval: 70, target: self, selector: #selector(timer6Event), userInfo: nil, repeats: true)
        
    }
    
    @objc func timer1Event(){
        print("timer1執(zhí)行")
        envent()
    }
    
    @objc func timer2Event(){
        print("timer2執(zhí)行")
        envent()
    }
    
    @objc func timer3Event(){
        print("timer3執(zhí)行")
        envent()
    }
    
    @objc func timer4Event(){
        print("timer4執(zhí)行")
        envent()
    }
    
    @objc func timer5Event(){
        print("timer5執(zhí)行")
        envent()
    }
    
    @objc func timer6Event(){
        print("timer6執(zhí)行")
        envent()
    }
    
    func envent(){
        
        for i in 2...10000{
            var sum = 1
            var j = 2
            while j <= Int(sqrt(Double(i))){
                if i % j == 0{
                    sum += j
                    if i / j != j{
                        sum += i / j
                    }
                }
                j += 1
            }
            if sum == i {
                print(i)
            }
        }
    }
}

按照上面的測試步驟,斷開電源線運行10分鐘。

驗證第二種方式

由于第一種方式所有定時器都是重復(fù)計時操作,為保證一致,這里只定義6個全局變量保存每個事件的計數(shù)器值,少定義6個全局變量判斷是否停止累加計數(shù)器(實際開發(fā)中用這種方式很繁雜,也許會停掉事件,但計時器掌管其他事件,不能停止,所以只能通過變量判斷是否需要繼續(xù)計數(shù))。

import UIKit

class ViewController: UIViewController {
    
    fileprivate var timer: Timer?
    
    fileprivate var counter1 = 0
    fileprivate var counter2 = 0
    fileprivate var counter3 = 0
    fileprivate var counter4 = 0
    fileprivate var counter5 = 0
    fileprivate var counter6 = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
        UIApplication.shared.isIdleTimerDisabled = true
        
        oneTimer()
    }

    func oneTimer(){
        
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(addCount), userInfo: nil, repeats: true)
    }
    
    @objc func addCount(){
        addCount1()
        addCount2()
        addCount3()
        addCount4()
        addCount5()
        addCount6()
    }
    
    func addCount1(){
        counter1 += 1
        guard counter1 >= 20 else { return }
        timer1Event()
        counter1 = 0
    }
    func addCount2(){
        counter2 += 1
        guard counter2 >= 30 else { return }
        timer2Event()
        counter2 = 0
    }
    func addCount3(){
        counter3 += 1
        guard counter3 >= 40 else { return }
        timer3Event()
        counter3 = 0
    }
    func addCount4(){
        counter4 += 1
        guard counter4 >= 50 else { return }
        timer4Event()
        counter4 = 0
    }
    func addCount5(){
        counter5 += 1
        guard counter5 >= 60 else { return }
        timer5Event()
        counter5 = 0
    }
    func addCount6(){
        counter6 += 1
        guard counter6 >= 70 else { return }
        timer6Event()
        counter6 = 0
    }
    
    func timer1Event(){
        print("timer1執(zhí)行")
        envent()
    }
    
    func timer2Event(){
        print("timer2執(zhí)行")
        envent()
    }
    
    func timer3Event(){
        print("timer3執(zhí)行")
        envent()
    }
    
    func timer4Event(){
        print("timer4執(zhí)行")
        envent()
    }
    
    func timer5Event(){
        print("timer5執(zhí)行")
        envent()
    }
    
    func timer6Event(){
        print("timer6執(zhí)行")
        envent()
    }
    
    func envent(){
        
        for i in 2...10000{
            var sum = 1
            var j = 2
            while j <= Int(sqrt(Double(i))){
                if i % j == 0{
                    sum += j
                    if i / j != j{
                        sum += i / j
                    }
                }
                j += 1
            }
            if sum == i {
                print(i)
            }
        }
    }
}

測試結(jié)果

電量

方式1耗電.png
方式2耗電.png

CPU

方式1CPU資源消耗.png
方式2CPU資源消耗.png

測試結(jié)果可以看出,耗電量兩個不相上下(20個等級,數(shù)字越大越耗電,這里全部是0),因為操作都過于簡單,幾乎不耗電,但是從CPU占用情況看。方式1更節(jié)約CPU資源,而CPU資源消耗跟耗電量成正比,所以有理由相信當(dāng)APP運行時間足夠長,方式2會消耗更多電量。

GCD方式實現(xiàn)定時器

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        createDispatchTimer(timeInterval: 2, handler: { [weak self] (timer) in
            self?.envent()
            
            //需要取消重復(fù)時調(diào)用
            //timer?.cancel()
        }, needRepeat: true)
    }
    
    
    ///   - timeInterval: 間隔時間
    ///   - handler: 事件
    ///   - needRepeat: 是否重復(fù)
    func createDispatchTimer(timeInterval: Double, handler:@escaping (DispatchSourceTimer?)->(), needRepeat: Bool)
    {
        let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
        timer.schedule(deadline: .now(), repeating: timeInterval)
        timer.setEventHandler {
            DispatchQueue.main.async {
                if needRepeat{
                    handler(timer)
                }else{
                    timer.cancel()
                    handler(nil)
                }
            }
        }
        timer.resume()
    }
    
    
    func envent(){
        print("執(zhí)行")
    }
    
    
    //附贈
    
    /// GCD定時器倒計時
    ///   - timeInterval: 間隔時間
    ///   - repeatCount: 重復(fù)次數(shù)
    ///   - handler: 循環(huán)事件, 閉包參數(shù): 1. timer, 2. 剩余執(zhí)行次數(shù)
    func createDispatchTimer(timeInterval: Double, repeatCount:Int, handler:@escaping (DispatchSourceTimer?, Int)->())
    {
        if repeatCount <= 0 {
            return
        }
        let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
        var count = repeatCount
        timer.schedule(wallDeadline: .now(), repeating: timeInterval)
        timer.setEventHandler(handler: {
            count -= 1
            DispatchQueue.main.async {
                handler(timer, count)
            }
            if count == 0 {
                timer.cancel()
            }
        })
        timer.resume()
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,282評論 8 265
  • OC語言基礎(chǔ) 1.類與對象 類方法 OC的類方法只有2種:靜態(tài)方法和實例方法兩種 在OC中,只要方法聲明在@int...
    奇異果好補閱讀 4,532評論 0 11
  • Runloop 是和線程緊密相關(guān)的一個基礎(chǔ)組件,是很多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不太會直接用到,...
    jackyshan閱讀 10,018評論 10 75
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,681評論 1 32
  • 每天以簡書的形式分享一篇繪本收獲、一篇育兒收獲,從暑假開始。
    J歡愈空間閱讀 164評論 0 0

友情鏈接更多精彩內(nèi)容