我有一个计时器,它是一个单身人士,每秒都会反复点火。我允许用户暂停计时器以及恢复。我正在跟踪计时器的开始日期,并且我从经过的时间中减去任何暂停。
不幸的是,我似乎无法修复一个间歇性问题,其中暂停和恢复计时器导致跳过一秒钟。
例如,在下面的代码块中,我启动计时器并打印秒:
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)
}
我做错了什么,间歇性地导致错过第二次?
答案 0 :(得分:3)
所以这里的问题是Timer
以这种方式不准确。或者更确切地说,它的计时相当准确,但实际射击速度有一些差异,因为它取决于跑道。
计时器不是实时机制;它只在其中一个发射时发射 已添加计时器的运行循环模式正在运行且能够运行 检查计时器的开火时间是否已经过去。
为了表明这一点,我摆脱了代码中的所有舍入并打印输出(你甚至不需要停下来看到这种情况发生)。以下是这种差异:
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)
}