带定时器的间歇性缺失秒数

时间:2016-10-13 21:36:45

标签: ios timer nstimer

我有一个计时器,它是一个单身人士,每秒都会反复点火。我允许用户暂停计时器以及恢复。我正在跟踪计时器的开始日期,并且我从经过的时间中减去任何暂停。

不幸的是,我似乎无法修复一个间歇性问题,其中暂停和恢复计时器导致跳过一秒钟。

例如,在下面的代码块中,我启动计时器并打印秒:

1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
12.0
13.0
14.0
15.0
16.0
17.0
18.0
19.0

在下面的代码块中,我恢复计时器,再次打印秒。但是,正如您所看到的,第20秒尚未打印出来:

21.0
22.0
23.0
24.0
25.0
26.0

我似乎无法弄清楚我在哪里输了第二个。每次暂停和恢复周期都不会发生这种情况。

我用来跟踪上述内容的属性如下:

/// The start date of the timer.
private var startDate = Date()

/// The pause date of the timer.
private var pauseDate = Date()

/// The number of paused seconds.
private var paused = TimeInterval()

/// The number of seconds that have elapsed since the initial fire.
private var elapsed = TimeInterval()

我通过创建计时器并设置开始日期来启动计时器:

/// Starts the shower timer.
func startTimer() {
    // Fire the timer every second.
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
    // Set the start time of the initial fire.
    startDate = Date()
}

如果用户暂停计时器,则执行以下方法:

/// Pauses the shower timer.
func pauseTimer() {
    // Pause the timer.
    timer?.invalidate()
    // Set the timer to `nil`, according to the documentation.
    timer = nil
    // Set the date of the pause.
    pauseDate = Date()
}

然后,当用户恢复计时器时执行以下方法:

/// Resumes the timer.
func resumeTimer() {
    // Recreate the timer.
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
    // Add the number of paused seconds to the `paused` property.
    paused += Date().timeIntervalSince(pauseDate)
}

以下方法由定时器触发时执行的方法调用,设置自初始触发以来经过的秒数,减去任何暂停的总和:

/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
    // Get the date for now.
    let now = Date()
    // Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
    elapsed = now.timeIntervalSince(startDate).rounded(.down).subtracting(paused.rounded(.down))
}

最后,以下方法是在计时器触发时执行的Selector

/// Updates the number of elapsed seconds since the timer has been firing.
@objc private func updateElapsedSeconds() {
    // Configure the elapsed time with each fire.
    updateElapsedTime()
    // Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
    NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}

我做错了什么,间歇性地导致错过第二次?

1 个答案:

答案 0 :(得分:3)

所以这里的问题是Timer以这种方式不准确。或者更确切地说,它的计时相当准确,但实际射击速度有一些差异,因为它取决于跑道。

From the documentation:

  

计时器不是实时机制;它只在其中一个发射时发射   已添加计时器的运行循环模式正在运行且能够运行   检查计时器的开火时间是否已经过去。

为了表明这一点,我摆脱了代码中的所有舍入并打印输出(你甚至不需要停下来看到这种情况发生)。以下是这种差异:

18.0004420280457
19.0005180239677
20.0004770159721
21.0005570054054
21.9997390508652
23.0003360509872
24.0003190040588
24.9993720054626
25.9991790056229

有时它发射得特别晚,这会导致整个事情被抛弃。舍入并没有帮助,因为您仍然依赖于实际参考时间的计时器,并且最终将关闭超过一秒。

根据您要完成的具体操作,有几种方法可以解决这种情况。如果你绝对需要实际时间,你可以调整计时器以在几分之一秒内触发,而是使用该输出来更精确地估计秒数。这是更多的工作,仍然不会完全正确(总会出现差异)。

根据您的代码,似乎只需根据计时器递增一个数字就足以实现您的目标。以下是对代码的简单修改,使其成功。无论你是否暂停,这都会很简单,永远不会在计数中跳过一秒:

/// The number of seconds that have elapsed since the initial fire.
private var elapsed = 0

private var timer: Timer?

/// Starts the shower timer.
func startTimer() {
    elapsed = 0
    // Fire the timer every second.
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}

/// Pauses the shower timer.
func pauseTimer() {
    // Pause the timer.
    timer?.invalidate()
    // Set the timer to `nil`, according to the documentation.
    timer = nil
}

/// Resumes the timer.
func resumeTimer() {
    // Recreate the timer.
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}

/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
    // Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
    elapsed += 1
    // debug print
    print(elapsed)
}

/// Updates the number of elapsed seconds since the timer has been firing.
@objc private func updateElapsedSeconds() {
    // Configure the elapsed time with each fire.
    updateElapsedTime()
    // Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
    NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}