iOS開發(fā)中的幾種定時器
iOS開發(fā)中定時器實現(xiàn)方式大致有三種,一種是Timer實現(xiàn),一種是通過GCD自己創(chuàng)建,另一種是CADisplayLink創(chuàng)建。
Timer使用簡單,需要注意的是Timer和runloop聯(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)定時器的方式。
本次驗證耗電采用Instrument的Energy Log工具,測試流程如下:
1.設(shè)置app不鎖屏:
UIApplication.shared.isIdleTimerDisabled = true2.安裝APP后斷開iPhone電源線
3.退出所有運行的APP
4.打開
系統(tǒng)設(shè)置-開發(fā)者-logging開啟Energy后點擊Start Recording5.開啟需要測試的APP運行10分鐘
6.打開
系統(tǒng)設(shè)置-開發(fā)者-logging點擊Stop Recording7.打開
Instrument的Energy 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é)果
電量


CPU


測試結(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()
}
}