第 11 节:计时器
第 11 节:计时器
重复和非重复的 Timer 在编码时总是有用的。除了异步执行代码之外,你通常还需要控制任务应该重复的时间和频率。
在 Dispatch 框架可用之前,开发人员依靠 RunLoop 来异步执行任务并实现并发。 你可以使用 Timer 创建重复和非重复 Timer。 随后,Apple 发布了 Dispatch 框架,包括 DispatchSourceTimer。
尽管以上所有方法都能够创建计时器,但在 Combine 中并非所有计时器都相同。
使用 RunLoop
主线程和你创建的任何线程(最好使用 Thread 类)可以拥有自己的 RunLoop。 只需从当前线程调用 RunLoop.current:如果需要,Foundation 会为你创建一个。请注意,除非你了解 RunLoop 是如何运行的——特别是你需要一个 RunLoop ——否则最好只使用运行应用程序主线程的主 RunLoop。
注意:Apple 文档中的一个重要说明和红灯警告是 RunLoop 类不是线程安全的。 你应该只为当前线程的 RunLoop 用 RunLoop 方法。
RunLoop 实现了你将后文中了解的调度程序协议。它定义了几种相对较低级别的方法,并且是唯一一种可以让你创建可取消计时器的方法:
此计时器不传递任何值,也不创建发布者。 它从 after: 参数中指定的日期开始,具有指定的间隔和容差,仅此而已。 它与 Combine 相关的唯一用处是它返回的 Cancelable 可让你在一段时间后停止计时器。
这方面的一个例子可能是:
但考虑到所有因素,RunLoop 并不是创建计时器的最佳方式。 使用 Timer 类会更好!
使用 Timer 类
Timer 是原始 Mac OS X 中可用的最古老的计时器,早在 Apple 将其重新命名为“macOS”之前。 由于它的委托模式和与 RunLoop 的紧密关系,它一直很难使用。 Combine 带来了一个现代变体,你可以直接用作发布者,而无需所有设置样板。
你可以通过以下方式创建重复计时器发布者:
on 和 in 两个参数确定:
你的计时器附加到哪个 RunLoop。 这里是主线程的 RunLoop。
计时器在哪个 RunLoop 模式下运行。 这里,默认的 RunLoop 模式。
除非你了解运行循环是如何运行的,否则你应该坚持使用这些默认值。 运行循环是 macOS 中异步事件源处理的基本机制,但它们的 API 有点繁琐。 你可以通过调用 RunLoop.current 为你自己创建或从 Foundation 获取的任何线程获取 RunLoop,因此你也可以编写以下代码:
注意:在 DispatchQueue.main 以外的 Dispatch 队列上运行此代码可能会导致不可预知的结果。 Dispatch 框架在不使用 RunLoop 的情况下管理其线程。 由于 RunLoop 需要调用其运行方法之一来处理事件,因此你永远不会看到计时器在除主队列之外的任何队列上触发。 保持安全并为你的计时器定位 RunLoop.main。
计时器返回的发布者是 ConnectablePublisher
。 它是 Publisher 的一个特殊变体,在你显式调用它的 connect()
方法之前,它不会在订阅时开始触发。 你还可以使用 autoconnect()
操作符,它会在第一个订阅者订阅时自动连接。
注意:你将在后文中了解有关可连接发布者的更多信息。
因此,创建将在订阅时启动计时器的发布者的最佳方法是编写:
计时器重复发出当前日期,其 Publisher.Output 类型为 Date。 你可以使用 scan 操作符制作一个发出递增值的计时器:
还有一个你在这里没有看到的额外 Timer.publish()
参数:容差(Tolerance)。 它以 TimeInterval 形式指定与你要求的持续时间的可接受偏差。但请注意,使用低于 RunLoop 的 minimumTolerance 值的值可能不会产生预期的结果。
使用 DispatchQueue
你可以使用调度队列来生成计时器事件。 虽然 Dispatch 框架有一个 DispatchTimerSource
事件源,但令人惊讶的是,Combine 没有为其提供计时器接口。 相反,你将使用另一种方法在队列中生成计时器事件。不过,这可能有点令人费解:
在前面的代码中:
创建一个 subject,你将向其发送计时器值。
准备一个 counter,每次计时器触发时,你都会增加它。
每秒在所选队列上安排一个重复操作。 动作立即开始。
订阅 subject 获取定时器值。
如你所见,这并不漂亮。 将此代码移动到函数并传递间隔和开始时间会有所帮助。
关键点
如果你怀念 Objective-C 代码,请使用旧的 RunLoop 类创建计时器。
使用 Timer.publish 获取发布者,该发布者在指定的 RunLoop 上以给定间隔生成值。
将 DispatchQueue.schedule 用于在调度队列上发出事件的现代计时器。
接下来去哪儿?
在后文中,你将学习如何编写自己的发布者,并且你将使用 DispatchSourceTimer 创建一个替代的计时器发布者。在此之前有很多东西要学,从下一节的 Key-Value Observing 开始。
Last updated