计时器间歇性漏秒
Intermittent Missing Seconds with Timer
我有一个定时器,它是一个单例,每秒重复触发。我允许用户暂停计时器以及恢复。我正在跟踪计时器的开始日期,并从经过的时间中减去任何暂停。
不幸的是,我似乎无法解决暂停和恢复计时器导致跳过一秒的间歇性问题。
例如,在下面的代码块中,我启动计时器并打印秒数:
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)
}
我做错了什么导致间歇性地漏秒?
所以这里的问题是 Timer
这种方式不准确。或者更确切地说,它的计时是相当准确的,但实际发射率有一些差异,因为它取决于运行循环。
A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed.
为了展示这一点,我去掉了您代码中的所有舍入并打印了输出(您甚至不需要停下来就可以看到这一点)。这是这个差异的样子:
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)
}
我有一个定时器,它是一个单例,每秒重复触发。我允许用户暂停计时器以及恢复。我正在跟踪计时器的开始日期,并从经过的时间中减去任何暂停。
不幸的是,我似乎无法解决暂停和恢复计时器导致跳过一秒的间歇性问题。
例如,在下面的代码块中,我启动计时器并打印秒数:
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)
}
我做错了什么导致间歇性地漏秒?
所以这里的问题是 Timer
这种方式不准确。或者更确切地说,它的计时是相当准确的,但实际发射率有一些差异,因为它取决于运行循环。
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed.
为了展示这一点,我去掉了您代码中的所有舍入并打印了输出(您甚至不需要停下来就可以看到这一点)。这是这个差异的样子:
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)
}