当系统时钟改变时,是否仍要运行代码?
例如:当时钟增加一分钟时,运行print(systemtime)
,我将当前时间存储在其中的变量。
这只是出于我自己的测试目的,现在我有一个计时器,每分钟重复一次,获取当前时间并打印出来。
但是,我正在寻找一种在iOS中将时间准确更改到控制台的方式print
。
从更一般的意义上讲,这是一种在iOS时钟更改时触发运行代码的方法。
最佳答案
一言以蔽之。 iOS上的实时时钟具有亚微秒级的精度。它使用一个double值来报告自iOS“epoch date”以来的时间(以分数秒为单位)。
以某个任意间隔(每秒60秒)调用一个函数不是系统函数。
也就是说,您可以设置一个与屏幕刷新同步的CADisplayLink
(低开销计时器),并且当时间在每分钟更改的某个阈值之内时(例如fmod(NSdate.timeIntervalSinceReferenceDate, 60) < 0.02
),您可以将消息记录到控制台。 d需要使用阈值,因为您设置的任何间隔都可能不会精确地出现在所需的标记上。
编辑
正如Matt在下面的注释中指出的那样,CADisplayLink
可能会导致设备CPU“热运行”并耗尽电池的速度比预期的更快。
如果您不需要毫秒级的精度,那么可以做一些数学运算来计算下一分钟的平均值,并在适当的延迟后启动一个重复计时器可能是一个不错的选择。像这样:
var timer: Timer?
func startMinuteTimer() {
let now = Date.timeIntervalSinceReferenceDate
let delayFraction = trunc(now) - now
//Caluclate a delay until the next even minute
let delay = 60.0 - Double(Int(now) % 60) + delayFraction
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
timer in
//Put your code that runs every minute here.
}
}
}
NSTimer(在Swift 3中为Timer)的分辨率大约为1/50秒,因此代码应将更新时间缩短到目标时间的0.02秒以内。另外,只要您的应用程序变为非 Activity 状态然后再次处于 Activity 状态,您就必须终止上述计时器并重新启动它,因为它将在您的应用程序不运行时暂停,并且即使在几分钟后也不会同步启动。
编辑#2:
正如我对答案的评论所指出的那样,上面的代码在开始执行后的第一分钟就无法启动。假设您要在每分钟运行的类中有一个timeFunction函数,则可以这样解决:
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.timeFunction() //Call your code that runs every minute the first time
self.timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
timer in
self.timeFunction() //Call your code that runs every minute.
}
}
编辑#3:我在示例iOS应用程序中使用了上面的代码,发现如果您暂停应用程序(将其交换到后台),则计时器将按预期停止触发。当您恢复应用程序时,计时器几乎立即触发,与“按时”时间不同步。然后在下一个分钟时间再次开始。
为了避免在接到恢复 call 时的下班时间 call ,您必须侦听暂停和恢复事件,使暂停事件的计时器无效,并在恢复事件时重新创建计时器。 (如果没有将您的应用设置为在后台运行,则无法使其在后台或手机被锁定时保持每分钟触发一次。)
编辑#4:
这是一个正在运行的“单视图”应用程序中的视图控制器代码,它完成了所描述的所有工作:添加willResignActiveNotification和didBecomeActiveNotification的观察者,启动单次计时器以与分钟的变化同步,然后每分钟重复一次计时器。它具有代码,可在每次触发计时器时在标签上显示时间,并在每次更新时将时间附加到文本视图中,以便您可以查看行为并记录暂停/继续事件。
它有2个网点:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var timeTextView: UITextView!
weak var timer: Timer?
var observer1, observer2: Any?
lazy var timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm.ss.SSS"
return formatter
}()
func appendStringToLog(string: String) {
let newTimeLogText = timeTextView.text + "\n" + string
timeTextView.text = newTimeLogText
}
func timeFunction() {
let timeString = timeFormatter.string(from: Date())
timeLabel.text = timeString
appendStringToLog(string: timeString)
print(timeString)
}
func startMinuteTimer() {
let now = Date.timeIntervalSinceReferenceDate
let delayFraction = trunc(now) - now
//Caluclate a delay until the next even minute
let delay = 60.0 - Double(Int(now) % 60) + delayFraction
//First use a one-shot timer to wait until we are on an even minute interval
self.timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { timer in
//The first time through, call the time function
self.timeFunction() //Call your code that runs every minute the first time
//Now create a repeating timer that fires once a minute.
self.timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
timer in
self.timeFunction() //Call your code that runs every minute.
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Observe the willResignActiveNotification so we can kill our timer
observer1 = NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { notification in
let string = "Suspending. Stopping timer at \(self.timeFormatter.string(from: Date()))"
print(string)
self.appendStringToLog(string: string)
self.timer?.invalidate()
}
//Observe the didBecomeActiveNotification so we can start our timer (gets called on app launch and on resume)
observer2 = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { notification in
let string = "Becoming active. Starting timer at \(self.timeFormatter.string(from: Date()))"
print(string)
self.appendStringToLog(string: string)
self.startMinuteTimer()
}
}
}