对于使用GCD的好处,大家可能在别的地方已经看到了和了解了。而如何使用GCD创建定时器也很简单。可是今天我要说的不是说如何创建的问题。而是在GCD定时器在使用上的一些奇怪现象!
简单的回顾:
1. 创建GCD定时器的代码如下:
var gcdTimer:DispatchSourceTimer?
func createGCDTimer() {
gcdTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global(qos: .default))
gcdTimer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .nanoseconds(0));
gcdTimer?.setEventHandler(handler: {
printXY("fire")
})
gcdTimer?.resume()
}
2. GCD提供的挂起、恢复、取消函数如下:
挂起函数:即为暂停定时器
gcdTimer?.suspend()
恢复函数:恢复和开始都调用该函数
gcdTimer?.resume()
取消函数:你可以理解为停止或者是销毁定时器
gcdTimer?.cancel()
可当你以为GCD定时器就这么简单,如此容易就完整某些功能的时候,请注意一下,坑可能已经悄悄的挖好了。
奇怪现象:
1. suspend()不可以对timer进行nil的设置
这样操作后,会得到一个报错信息:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
-
原因:简单来说,就是GCD的文档里面写了suspend()和resume()这两个函数需要配对使用.而当你挂起了定时器后,系统会记录挂起数,如果挂起数不为0,强行设置nil就会出现异常了。
-
解决办法:添加一个变量去标志当前是否已经暂停了。因为GCD中并没有提供任何方法去获取当前定时器的状态.
2. 调用suspend(),再调用resume()会发现,重新恢复后eventHandler的前两次调用的时间间隔是错误的
- 首先上我的测试代码
var gcdTimer:DispatchSourceTimer?
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
gcdTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global(qos: .default))
gcdTimer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .nanoseconds(0));
gcdTimer?.setEventHandler(handler: {
self.count += 1
printXY("eventHandler \(self.count)")
})
gcdTimer?.resume()
}
@IBAction func pauseC(_ sender: Any) {
gcdTimer?.suspend()
}
@IBAction func resumeC(_ sender: Any) {
gcdTimer?.resume()
}
代码很简单,简单描述一下:
- 就是在viewDidLoad中创建了一个间隔为1秒的的定时器。然后立即开启定时器。
EventHandler: 输出当前调用了多少次pauseC: 暂停函数的按钮事件resumeC: 恢复函数的按钮事件
我们来看看定时器刚刚开启的时候的输出,还是很正常,基本上就是一秒一个输出。如图:
2021-03-23 14:21:26.840 eventHandler 1
2021-03-23 14:21:27.840 eventHandler 2
2021-03-23 14:21:28.840 eventHandler 3
2021-03-23 14:21:29.839 eventHandler 4
2021-03-23 14:21:30.840 eventHandler 5
2021-03-23 14:21:31.840 eventHandler 6
2021-03-23 14:21:32.840 eventHandler 7
2021-03-23 14:21:33.839 eventHandler 8
2021-03-23 14:21:34.840 eventHandler 9
2021-03-23 14:21:35.840 eventHandler 10
2021-03-23 14:21:36.840 eventHandler 11
2021-03-23 14:21:37.839 eventHandler 12
2021-03-23 14:21:38.840 eventHandler 13
可在我调用了suspend()后,再调用resume()后,会发现在重新恢复的时候,会发现第1次和第2次的间隔有问题。如图:
2021-03-23 14:21:38.840 eventHandler 13
2021-03-23 14:21:39.752 suspend
2021-03-23 14:21:43.518 resume
2021-03-23 14:21:43.519 eventHandler 14
2021-03-23 14:21:43.840 eventHandler 15
2021-03-23 14:21:44.841 eventHandler 16
2021-03-23 14:21:45.840 eventHandler 17
2021-03-23 14:21:46.840 eventHandler 18
2021-03-23 14:21:47.841 eventHandler 19
2021-03-23 14:21:48.840 eventHandler 20
注意第14次输出和第15次输出,他们之间的间隔是不到一秒的。
2021-03-23 14:21:43.519 eventHandler 14
2021-03-23 14:21:43.840 eventHandler 15
这就很尴尬了。如果你是在做一个倒计时的功能或者其他需求的时候,因为某种原因导致了暂停再恢复的操作,就会出现一个时间加速的效果。
解决方案:
而对于这个现象,我的解决方法是,
- 不使用GCD提供的suspend()。
- 暂停的功能调用cancel()函数。
- 而在调用resume()进行恢复的时候,判断timer是否存在,存在则直接调用resume(),不存在则重新实例化定时器,再调用resume()
具体的代码可以参考我的Github代码,因为代码有点多。大家可以移步到这里