如何在指定的 date/time 调用 Swift 函数

How to call Swift function at specified date/time

在 iOS 的 Swift 中精确(1 秒内)date/time 触发 function/block 的最佳方法是什么?

我试过将 TimerinitfireAt 一起使用,但我发现精度通常会相差几秒钟。将计时器精度设置为 0.5 之类的值并没有产生更好的结果。

我也尝试过将 GCD 与 DispatchQueue.main.asyncAfter(.now() + someDelay) 一起使用,并且在我的测试中也经常会出现几秒钟的问题。

我决定创建一个每秒触发一次的计时器,然后我检查当前 date/time 是否已超过我想要的值,然后我手动触发我的代码。似乎有更好的方法来解决这个问题,但我没有主意。谢谢!

如果你用 makeTimerSource with a flag of .strict 创建一个 GCD 计时器,它会选择退出计时器合并:

The system makes its best effort to observe the timer’s specified leeway value, even if the value is smaller than the default leeway.

例如:

var timer: DispatchSourceTimer?

func startTimer(in interval: DispatchTimeInterval, block: @escaping () -> Void) {
    timer = DispatchSource.makeTimerSource(flags: .strict, queue: .main)
    timer?.setEventHandler(handler: block)
    timer?.schedule(deadline: .now() + interval, leeway: .milliseconds(500))
    timer?.activate()
}

然后:

startTimer(in: .seconds(240)) { [weak self] in
    self?.foo()
}

请注意,与 Timer 不同,它由您安排它的 RunLoop 保留,您必须保留自己对 GCD 计时器的强引用,如上所示。您可能会创建 Timer 属性 weak(以便在 RunLoop 释放它们时释放它们),但您需要对 GCD 计时器的强引用。

一些注意事项:

  • 注意[weak self]捕获列表的使用。您通常希望避免强引用循环(尤其是当计时器是重复计时器时)。

  • 只有在绝对需要时才应使用此模式,因为您选择退出计时器合并提供的节能功能。我希望这比重复计时器方法更节能,但比让 OS 使用其自由裁量权来合并其计时器更节能。

  • 此模式假设其调度的队列未被阻塞。显然,我们从不阻塞主线程,但您始终可以考虑使用自己的目标队列。

  • 为了未来的读者,当应用程序实际上是 运行 时,此模式显然适用。如果应用程序被挂起,计时器将无法触发。如果您希望在指定时间通知用户,即使该应用当前未 运行,您可以使用本地计划的 user notification.