当时间(iOS 时钟)发生变化时,有没有办法运行代码?

Jol*_*oux 1 ios swift

当系统时钟发生变化时,无论如何要运行代码吗?

例如:当时钟增加一分钟时,运行print(systemtime)我存储当前时间的变量。

这只是为了我自己的测试目的,现在我有一个计时器,它每分钟重复一次,获取当前时间并打印出来。

但是,我正在寻找一种print在 iOS 中准确更改控制台时间的方法。

在更一般的意义上,一种在 iOS 时钟变化时触发运行代码的方法。

Dun*_*n C 5

一句话,没有。iOS 上的实时时钟具有亚微秒级精度。它使用 double 来报告自 iOS“纪元日期”以来的小数秒时间。

在某个任意时间间隔(60 秒,在第二个)调用函数不是系统函数。

也就是说,您可以设置一个CADisplayLink与屏幕刷新同步的(低开销计时器),并且当时间在一分钟更改的某个阈值内时”(fmod(NSdate.timeIntervalSinceReferenceDate, 60) < 0.02例如)您可以将您的消息记录到控制台。您需要使用阈值,因为您设置的任何间隔可能不会恰好出现在所需标记上。

编辑

正如马特在下面的评论中指出的那样,aCADisplayLink可能会导致设备 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.
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

NSTimer(Timer,在 Swift 3 中)的分辨率应该在 1/50 秒左右,所以代码应该让你的更新在你的目标时间的大约 0.02 秒内。

此外,任何时候您的应用程序变为非活动状态然后再次活动时,您都必须终止上述计时器并重新启动它,因为它会在您的应用程序未运行时暂停,并且不会再同步以在几分钟内触发.

编辑#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.
    }
  }
Run Code Online (Sandbox Code Playgroud)

编辑#3:

我在一个示例 iOS 应用程序中使用了上面的代码,发现如果您暂停应用程序(将其切换到后台),那么计时器将按预期停止触发。当您恢复应用程序时,计时器几乎立即触发,与“实时”时间不同步。然后它在下一个整分钟时间再次首先。

为了避免在您接到恢复呼叫时的非工作时间呼叫,您必须监听挂起和恢复事件,在挂起事件上使您的计时器无效,并在恢复事件上重新创建您的计时器。(如果不将您的应用程序设置为在后台运行,则无法让它在后台运行或手机锁定时每分钟都触发一次。)

编辑#4:

这是来自一个工作“单一视图”应用程序的视图控制器代码,它完成了描述的所有事情:为 willResignActiveNotification 和 didBecomeActiveNotification 添加观察者,启动单次计时器以与分钟的变化同步,然后每分钟重复一次计时器。它有代码在每次计时器触发时显示时间到标签,并且每次更新时都会将时间附加到文本视图,以便您可以查看行为,并记录挂起/恢复事件。

它有2个出口:

  • timeLabel(显示当前时间的 UILabel)
  • timeTextView:显示时间日志和挂起/恢复事件的 UITextView。
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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)