iOS开发中的几种定时器
iOS开发中定时器实现方式大致有三种,一种是Timer
实现,一种是通过GCD
自己创建,另一种是CADisplayLink
创建。
Timer
使用简单,需要注意的是Timer
和runloop
联系紧密,在用scheduled方式创建之后,程序会自动将其添加到runloop中,不再需要Timer时,必须手动将其释放。
CADisplayLink
与屏幕刷新率同步,也就是每当屏幕刷新一次就可以执行一次绑定的事件(iPhone的刷新频率为60HZ,正常情况下1s需要刷新60次),也可以通过其属性设置间隔多少帧执行一次,一般用于即时画面渲染,启动CADisplayLink
定时器需要调用add(to: <#T##RunLoop#>, forMode: <#T##RunLoopMode#>)
方法。
gcd定时器主要用于精准定时,它的精度能达到纳秒级别。gcd不同于上面两种定时器,它不会受runloop的影响(runloop也是通过gcd实现),所以精确,例如Timer加入runloop默认模式时,当有滑动控件在滑动时会暂停计时,但是gcd不会。gcd可控性强,但是使用稍微复杂
定时器普通模式下不能在后台运行,需要设置Background Modes,选择一种可以后台运行的模式
关于耗电测试
有时候开发需要多个任务定时执行(与硬件结合开发较常见),可能他们的定时时间不一致,这就涉及到多个计时。
我试想了两种实现方式,一种是通过创建多个定时器实现,另一种是创建一个定时器,每个定时任务定义一个计数器,定时器每秒累加他们的计数器,当满足时间条件时就去执行相应的定时任务,当需要取消某个任务时就停止累加并将计数器置零。
比较一下这两种方式,第一种简单明了,便于维护,但是会存在多个定时器,消耗内存资源。第二种只有一个定时器,但是代码易读性相对第一种较差,也会创建很多全局变量判断是否需要累加计数器以及保存计数器的值。而且定时器执行间隔是1s,执行内容是将所有任务计数器累加1,判断每个任务的计数器值是否满足执行条件,耗费CPU资源。
虽然每个定时器都会消耗资源,但我个人还是认为第一种方式较为科学,耗电也不会比第二种高,因为第二种每秒都会执行任务消耗CPU,比如一个10秒的定时任务,第一种方式执行1次,第二种方式需要执行10次,也不够灵活,管理多个任务的时候更难操控。接下来我需要验证一下这个想法,我准备先用Timer实现验证。最后附带gcd实现定时器的方式。
本次验证耗电采用Instrument
的Energy Log
工具,测试流程如下:
1.设置app不锁屏:
UIApplication.shared.isIdleTimerDisabled = true
2.安装APP后断开iPhone电源线
3.退出所有运行的APP
4.打开
系统设置-开发者-logging
开启Energy
后点击Start Recording
5.开启需要测试的APP运行10分钟
6.打开
系统设置-开发者-logging
点击Stop Recording
7.打开
Instrument
的Energy Log
工具,从设备导出日志
8.修改方式后重新执行以上步骤
验证第一种实现方式
定义6个定时器控制6个任务,每个任务时间不一致。
为了有操作消耗CPU资源,这里任务都是计算0-10000之间的完全数
完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数。它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身。如果一个数恰好等于它的因子之和,则称该数为“完全数”。
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执行")
envent()
}
@objc func timer2Event(){
print("timer2执行")
envent()
}
@objc func timer3Event(){
print("timer3执行")
envent()
}
@objc func timer4Event(){
print("timer4执行")
envent()
}
@objc func timer5Event(){
print("timer5执行")
envent()
}
@objc func timer6Event(){
print("timer6执行")
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分钟。
验证第二种方式
由于第一种方式所有定时器都是重复计时操作,为保证一致,这里只定义6个全局变量保存每个事件的计数器值,少定义6个全局变量判断是否停止累加计数器(实际开发中用这种方式很繁杂,也许会停掉事件,但计时器掌管其他事件,不能停止,所以只能通过变量判断是否需要继续计数)。
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执行")
envent()
}
func timer2Event(){
print("timer2执行")
envent()
}
func timer3Event(){
print("timer3执行")
envent()
}
func timer4Event(){
print("timer4执行")
envent()
}
func timer5Event(){
print("timer5执行")
envent()
}
func timer6Event(){
print("timer6执行")
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)
}
}
}
}
测试结果
电量
CPU
测试结果可以看出,耗电量两个不相上下(20个等级,数字越大越耗电,这里全部是0),因为操作都过于简单,几乎不耗电,但是从CPU占用情况看。方式1更节约CPU资源,而CPU资源消耗跟耗电量成正比,所以有理由相信当APP运行时间足够长,方式2会消耗更多电量。
GCD方式实现定时器
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
createDispatchTimer(timeInterval: 2, handler: { [weak self] (timer) in
self?.envent()
//需要取消重复时调用
//timer?.cancel()
}, needRepeat: true)
}
/// - timeInterval: 间隔时间
/// - handler: 事件
/// - needRepeat: 是否重复
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("执行")
}
//附赠
/// GCD定时器倒计时
/// - timeInterval: 间隔时间
/// - repeatCount: 重复次数
/// - handler: 循环事件, 闭包参数: 1. timer, 2. 剩余执行次数
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()
}
}