数字电路:五分钟计时器
Hi everybody, I’m Riccardo. Senior iOS Engineer at Bending Spoons, I breathe iOS development, both apps and tools and I love to share my knowledge with others
大家好,我是Riccardo。 Bending Spoons的高级iOS工程师,我负责iOS开发,包括应用程序和工具,我喜欢与他人分享我的知识
Sooner or later, we need to write some code that involves timers. Displaying a count-down? You may need a timer. Showing a continuous animation in discrete steps? You may need a timer. Performing an operation every x
seconds? You’ll probably need a timer.
迟早,我们需要编写一些涉及计时器的代码。 显示倒数? 您可能需要一个计时器。 在不连续的步骤中显示连续的动画? 您可能需要一个计时器。 每x
秒执行一次操作? 您可能需要一个计时器。
Apple offers different options for timers, every one with its own features. The main ones are:
Apple为计时器提供了不同的选项,每个选项都有自己的功能。 主要的是:
Timer
: a general-purpose timer.Timer
:通用计时器。CADisplayLink
: a timer whose firing rate is bound to the screen refresh rate.CADisplayLink
:一个计时器,其触发频率与屏幕刷新率绑定。DispatchSourceTimer
: a versatile timer that monitors the time events of the OS.DispatchSourceTimer
:多功能计时器,用于监视操作系统的时间事件。
计时器 (Timer)
The first and the most common option: the classic Timer
.
第一个也是最常见的选择:经典Timer
。
Why is this so common? It is easy to create it and run it, and it offers some methods to customize it. A typical code to schedule a recurring timer looks like this:
为什么这么常见? 创建和运行它很容易,并且它提供了一些自定义它的方法。 安排循环计时器的典型代码如下:
The returned object offers some interesting methods and properties:
返回的对象提供了一些有趣的方法和属性:
fire()
to force it to fire immediately.fire()
强制其立即射击。invalidate()
to stop it.invalidate()
停止它。fireDate
property to check when it will fire again.fireDate
属性以检查何时会再次触发。tolerance
to set a tolerance for the next firing time.tolerance
设置下一次点火时间的容差。
It looks like this timer provides everything we need! But what limitations does it have?
看来此计时器提供了我们所需的一切! 但是它有什么限制?
If the UI is performing complex operations, like scrolling a collection view with big images, the timer will fire with an unpredictable delay. The fire interval will not be respected! From the documentation:
如果UI正在执行复杂的操作(例如,滚动带有大图像的收藏夹视图),则计时器将以不可预测的延迟触发。 开火间隔将不予遵守! 从文档中:
A timer is not a real-time mechanism. If a timer’s firing time occurs during a long run loop callout or while the run loop is in a mode that isn’t monitoring the timer, the timer doesn’t fire until the next time the run loop checks the timer. Therefore, the actual time at which a timer fires can be significantly later.
计时器不是实时机制。 如果计时器的触发时间发生在长时间运行的循环调用期间或运行循环处于不监视计时器的模式下,则直到下次运行循环检查计时器时,计时器才会启动。 因此,计时器触发的实际时间可能要晚得多。
It has another shortcoming: if scheduled from a secondary thread, it won’t fire. A Timer
attach itself to the RunLoop
associated with the thread from where it is scheduled. By default, secondary thread in iOS does not have a RunLoop
and it’s the developer's responsibility to create and manage it.
它还有另一个缺点:如果从辅助线程进行调度,则不会触发。 Timer
将自己附加到与RunLoop
线程相关联的RunLoop
。 默认情况下,iOS中的辅助线程没有RunLoop
,创建和管理它是开发人员的责任。
So, which other tools do we have to address these issues?
那么,我们还必须解决哪些其他工具?
CADisplayLink (CADisplayLink)
The perfect tool for a precise ticking is the CADisplayLink
.
CADisplayLink
是精确滴答的完美工具。
This object comes from the CoreAnimation
framework and it is extremely precise because it is invoked every time the screen is refreshed. The screen refresh rate should always be at 60 frames per second: so it is invoked 60 times every second.
该对象来自CoreAnimation
框架,它非常精确,因为每次刷新屏幕时都会调用该对象。 屏幕刷新率应始终为每秒60帧:因此,每秒刷新60次。
The typical code to schedule a display link looks like the following:
安排显示链接的典型代码如下所示:
For the CADisplayLink
to work as we want, we need to perform some additional operations:
为了使CADisplayLink
能够按需要工作,我们需要执行一些其他操作:
- We need to attach it manually to a run loop. Otherwise, the timer will be created but it won’t fire. 我们需要将其手动附加到运行循环。 否则,将创建计时器,但不会触发。
We need to perform a bit of simple math. The display link will fire 60 times every second and, in general, we don’t have to execute its callback that often. The difference between the
displayLink.timestamp
and thelastTick
is used to execute the callback every time interval we need.我们需要执行一些简单的数学运算。 显示链接每秒将触发60次,通常,我们不必经常执行其回调。
displayLink.timestamp
和lastTick
之间的差异用于在我们需要的每个时间间隔执行回调。
The display link offers the same invalidate
method of the Timer
to stop it.
显示链接提供了与Timer
相同的invalidate
Timer
停止的方法。
However, also CADisplayLink
won’t fire when it’s started in the background.
但是, CADisplayLink
在后台启动时也不会触发。
DispatchSourceTimer (DispatchSourceTimer)
The last tool I’d like to talk about is the DispatchSourceTimer
. This is quite a rare timer that uses a DispatchSource
to monitor the time events of the OS.
我想谈的最后一个工具是DispatchSourceTimer
。 这是一个非常罕见的计时器,它使用DispatchSource
监视操作系统的时间事件。
The code to schedule this timer looks like this:
安排此计时器的代码如下所示:
Like in the CADisplayLink
case, the DispatchSourceTimer
requires a bit of setup:
像在CADisplayLink
一样, DispatchSourceTimer
需要一些设置:
We need to create the timer. We can pass a flag, in the example above
.strict
, and the queue in which it will be executed. This is the trick to have it working in a background thread.我们需要创建计时器。 在上面的示例中,我们可以传递一个标志
.strict
,并在其中执行该标志。 这是使它在后台线程中工作的技巧。set up a
handler
to do the work we need every time it fires.设置
handler
来执行每次触发时我们需要的工作。use the
schedule(deadline:, repeating:, leeway:)
method to define the first execution, the repeated executions, and an eventual tolerance使用
schedule(deadline:, repeating:, leeway:)
方法定义第一次执行,重复执行以及最终的容忍度invoke
resume()
to actually start it.调用
resume()
实际启动它。
There is another tricky detail about DispatchSourceTimer
: we have to maintain a strong reference to it. In the case of Timer
and CADisplayLink
, it is the RunLoop
in which they are executed that retains their references. That’s not the case for the DispatchSourceTimer
and it’s our responsibility to keep it alive.
关于DispatchSourceTimer
还有另一个棘手的细节:我们必须对其进行严格的引用。 对于Timer
和CADisplayLink
,将在其中执行它们的RunLoop
保留其引用。 DispatchSourceTimer
并非如此,保持它的生命是我们的责任。
When do we need such a timer? For example, when we want to send measurements to a server at some specific intervals without weighing too much on the system’s resources.
我们什么时候需要这样的计时器? 例如,当我们希望以特定的时间间隔将测量结果发送到服务器而又不会过多地占用系统资源时。
结论 (Conclusion)
Running a timer looks like a trivial matter but that’s not always the case. Apple offers several tools to face the challenges of a repeated task and, as usual, the more we know the better we can tackle our problems.
运行计时器看起来很简单,但并非总是如此。 Apple提供了多种工具来应对重复任务的挑战,并且像往常一样,我们知道的越多,就能越好地解决问题。
The Timer
class is the most general and the simplest one. It is the first that we should try to use, especially if we don’t have any special needs. If we do, however, we can consider one of the other tools: if we have to be extremely precise, we can go with the CADisplayLink
; if we need to run the timer from a secondary thread, we can use the DispatchSourceTimer
.
Timer
类是最通用和最简单的类。 这是我们应该尝试使用的第一个方法,特别是如果我们没有任何特殊需要时。 但是,如果这样做,我们可以考虑使用其他工具之一:如果必须非常精确,则可以使用CADisplayLink
。 如果需要从辅助线程运行计时器,则可以使用DispatchSourceTimer
。
Try always to choose the right tool for the right job!
始终尝试为正确的工作选择正确的工具!
翻译自: https://medium.com/swlh/tic-toc-what-timer-is-it-2200458c86f5
数字电路:五分钟计时器