SwiftUI:使用计时器重复触发事件

时间:2022-07-28
本文章向大家介绍SwiftUI:使用计时器重复触发事件,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

iOS带有内置的Timer类,使我们可以定期运行代码。它使用来自 Apple 框架称为 Combine 的发布者系统。实际上,在本系列的许多应用中,我们实际上一直在使用 Combine 的一部分,尽管您不太可能会注意到它。例如,@Published属性包装器和ObservableObject协议都来自 Combine,但是我们不需要知道,因为当您导入SwiftUI时,我们也会隐式地导入 Combine 的一部分。

Apple 的核心系统库称为 Foundation,它为我们提供了诸如 DataDateNSSortDescriptorUserDefaults之类的功能。它还为我们提供了Timer类,该类旨在在一定的秒数后运行函数,但它也可以重复运行代码。Combine 对此添加了扩展,以便计时器可以成为发布者,这些发布者会在其值更改时宣布。这是@Published属性包装器的名称来源,计时器发布者以相同的方式工作:到达您的时间间隔时,Combine 将发出包含当前日期和时间的公告。

创建计时器发布者的代码如下所示:

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

它一次性做了这几件事:

  1. 它要求计时器每1秒触发一次。
  2. 它说计时器应该在主线程上运行。
  3. 它说计时器应该在 Common Mode Run Loop 中运行,这是您大部分时间要使用的循环。(这个类型的 Run Loop 使 iOS 在用户积极执行某项操作(例如滚动列表)时可以处理运行中的代码。)
  4. 它会立即连接计时器,这意味着它将开始计时。
  5. 它将整个事物分配给timer常量,以便它保持活动状态。

如果您还记得的话,在项目7中我曾说过“@Published大约是@State的一半” ——它发送变更公告,其他人可以监视。对于像这样的常规发布者,我们需要使用名为onReceive()的新修饰符来手动捕获公告。这将接受发布者作为其第一个参数,并接受要作为其第二个函数运行的函数,并且它将确保在发布者发送其更改通知时都会调用该函数。

对于我们的计时器示例,我们可以收到如下通知:

Text("Hello, World!")
    .onReceive(timer) { time in
        print("The time is now (time)")
    }

这将每秒打印一次时间,直到计时器最终停止。

说到停止计时器,需要花一点时间才能停止我们创建的计时器。您会看到,我们制作的timer属性是一个自动连接的发布者,因此我们需要转到其上游发布者来查找计时器本身。从那里我们可以连接到计时器发布者,并要求它取消自身。老实说,如果不是为了完成代码,这将很难找到,但是它在代码中的外观如下:

self.timer.upstream.connect().cancel()

例如,我们可以更新现有示例,以使其仅触发计时器五次,如下所示:

struct ContentView: View {
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var counter = 0

    var body: some View {
        Text("Hello, World!")
            .onReceive(timer) { time in
                if self.counter == 5 {
                    self.timer.upstream.connect().cancel()
                } else {
                    print("The time is now (time)")
                }

                self.counter += 1
            }
    }
}

在结束之前,我想向您展示一个更重要的计时器概念:如果您对计时器有一点浮动感到满意,则可以指定一些公差。这使iOS可以执行重要的耗能优化,因为iOS可以在其计划的启动时间与计划的启动时间加您指定的容差之间的任何时间触发计时器。实际上,这意味着系统可以执行计时器合并:它可以将计时器稍微向后推,以便与一个或多个其他计时器同时触发,这意味着它可以使CPU保持更多的空闲时间并节省电池电量。

例如,这为我们的计时器增加了半秒的容限:

let timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect()

如果您需要严格保持时间,那么不使用公差(tolerance)参数将使您的计时器尽可能准确,但是请注意,即使没有任何公差,Timer类也仍然是“尽力而为”的做法——系统无法保证它将准确执行。

译者注: Common Modes 不是一种真正的RunLoop Mode,是一种伪模式,指可以在标记为Common Modes的 RunLoop 模式下运行

目前被标记为 Common Modes 的模式: kCFRunLoopDefaultModeUITrackingRunLoopMode

所以类似 Timer 添加到 Common Mode 的时候,并不是真正的添加了,他查找他的modes, 然后给他的modes 添加 Timer即实际上,Timer 添加到了 Default 和 Tracking模式。

译自 Triggering events repeatedly using a timer