·
·
文章目录
  1. NSTimer 问题
    1. 1.循环引用
    2. 2.Runloop 导致计时不准确
    3. 3.线程问题
  2. CADisplayLink
  3. GCD
  4. 参考

设计一个简单准确的定时器

NSTimer 问题

项目中有定时刷新的功能需求,使用 NSTimer 一把梭之后发现了如下的问题,我们首先写一段示例代码:

nstimer

1.循环引用

timer 会强引用 target,而 target 又强引用者 timer 对象,最后就会导致当前 viewcontroller 不会被释放,deinit 方法也不会被调用,从而产生了循环引用。

第一种可以通过 Timer 的另一个方法,不去使用 target,在 block 中弱引用 self,可以解决循环引用的问题,代码如下。
nstimer_block

第二种方式可以通过引入一个中间对象,让 target 强引用中间对象,中间对象在弱引用 timer,这样一旦 viewcontroller 被释放掉,deinit 方法就会被调用,timer 就会被释放掉。但是这样存在一个问题是,中间对象其实是无法响应 selector,会导致崩溃。这样就是需要用到消息转发机制,将所有中间对象收到的事件都转发给 self.target 进行响应,代码如下。
主类的代码:
nstimer_myproxy

中间对象 MyProxy 的代码:
myproxy

其他还有些方式,可以看看这篇博客 iOS之NSTimer循环引用的解决方案 - 掘金,这边不做详细介绍了。

2.Runloop 导致计时不准确

由于 NSTimer 是依赖于 RunLoop 机制的,所以会因为 Runloop 的问题导致计时不准确。上面两个 Timer.scheduledTimer 初始化方法都是默认运行在 Runloop 的 default mode 中。

在 ScrollView 在滑动的过程中,主线程的 Runloop 会切换到 UITrackingRunLoopMode ,这个时候 timer 就不会运行,就会导致计时不准确。如果想要滑动的时候不失效,可以将 timer 运行在 NSRunLoopCommonModes

3.线程问题

同样由于 NSTimer 是依赖于 RunLoop 机制的,所以在子线程中初始化一个 timer 默认是不会运行的。原因是因为子线程并没有创建 Runloop。

CADisplayLink

官方文档对于 CADisplayLink 的介绍是:

A timer object that allows your application to synchronize its drawing to the refresh rate of the display.

一般情况下,我们的屏幕刷新率是1/60s 一次。CADisplayLink 跟 NSTimer 的用法基本相似,NSTimer 的时间间隔是以秒为单位,而 CADisplayLink 是使用帧率来作为时间间隔的单位。

所以说 CADisplayLink 最常见到的应用场景就是写一个监测 FPS 的工具,我这里引用 YYFPSLabel 的代码实现大概介绍下原理:

yyfpslabel

我们可以看到,使用方法与 NSTimer 基本一致,同样是使用了 YYWeakProxy 来避免循环引用,且 CADisplayLink 还需要手动添加到 Runloop 中。那么问题在哪里?问题就出在 CADisplayLink 仍然是基于 Runloop 来实现的,而 RunLoop 的运行取决于其所在的 mode 以及 CPU 的繁忙程度,当 CPU 忙于计算显示内容或者 GPU 工作太繁重时,就会导致显示出来的 FPS 与 Instrument 的不一致。所以说基于CADisplayLink实现的 FPS 无法完全检测出当前 Core Animation 的性能情况,它只能检测出当前 RunLoop 的帧率。

GCD

最后我们说到今天的重点,通过 GCD 来实现 Timer,主要是使用 DispatchSource。步骤如下:创建一个监听的事件类型对应的 dispatch source,然后给这个 source 指定闭包和 Dispatch Queue。当 source 监听到相应的事件时,就会将该闭包自动加到 queue 中执行。代码如下,引用自 GitHub - 100mango/SwiftTimer: Simple and Elegant Timer

swifttimer

我们再看看代码,循环引用、计时不准确、线程问题都得到了解决,相比较除了代码量稍微多一些,在精度方面无疑是最好的。

参考

**版权声明**

Ivan’s Blog by Ivan Ye is licensed under a Creative Commons BY-NC-ND 4.0 International License.
叶帆创作并维护的叶帆的博客博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证

本文首发于Ivan’s Blog | 叶帆的博客博客( http://yeziahehe.com ),版权所有,侵权必究。

本文链接:http://yeziahehe.com/2020/04/26/design_a_simple_timer/

支持一下
扫一扫,支持yeziahehe
  • 微信扫一扫
  • 支付宝扫一扫