Measuring Time Accurately in Swift for Comparison Across Devices

Measuring Time Accurately in Swift for Comparison Across Devices

You can use NSTimeInterval to measure time (much better than a timer). You just need to store two dates (two points in time) and subtract endTime - StartTime as follow:

import UIKit

class ViewController: UIViewController {

var startTime: TimeInterval = 0
var endTime: TimeInterval = 0

override func viewDidLoad() {
super.viewDidLoad()
startTime = Date().timeIntervalSinceReferenceDate
}

@IBAction func stopTimeAction(_ sender: Any) {
endTime = Date().timeIntervalSinceReferenceDate
print((endTime-startTime).time)
}

}

extension TimeInterval {
var time: String { .init(format: "%d:%02d:%02d.%03d", Int(self/3600),
Int((self/60).truncatingRemainder(dividingBy: 60)),
Int(truncatingRemainder(dividingBy: 60)),
Int((self*1000).truncatingRemainder(dividingBy: 1000))) }
}

How do I achieve very accurate timing in Swift?

For acceptable musically accurate rhythms, the only suitable timing source is using Core Audio or AVFoundation.

Measure elapsed time in Swift

Update

With Swift 5.7, I think everything below becomes obsolete. Swift 5.7 introduces the concept of a Clock which appears to have a function designed to do exactly what is required here.

I'll update with an example as soon as I've got Swift 5.7 and have the time to rework it.


Here's a Swift function I wrote to measure Project Euler problems in Swift

As of Swift 3, there is now a version of Grand Central Dispatch that is "swiftified". So the correct answer is probably to use the DispatchTime API.

My function would look something like:

// Swift 3
func evaluateProblem(problemNumber: Int, problemBlock: () -> Int) -> Answer
{
print("Evaluating problem \(problemNumber)")

let start = DispatchTime.now() // <<<<<<<<<< Start time
let myGuess = problemBlock()
let end = DispatchTime.now() // <<<<<<<<<< end time

let theAnswer = self.checkAnswer(answerNum: "\(problemNumber)", guess: myGuess)

let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // <<<<< Difference in nano seconds (UInt64)
let timeInterval = Double(nanoTime) / 1_000_000_000 // Technically could overflow for long running tests

print("Time to evaluate problem \(problemNumber): \(timeInterval) seconds")
return theAnswer
}


Old answer

For Swift 1 and 2, my function uses NSDate:

// Swift 1
func evaluateProblem(problemNumber: Int, problemBlock: () -> Int) -> Answer
{
println("Evaluating problem \(problemNumber)")

let start = NSDate() // <<<<<<<<<< Start time
let myGuess = problemBlock()
let end = NSDate() // <<<<<<<<<< end time

let theAnswer = self.checkAnswer(answerNum: "\(problemNumber)", guess: myGuess)

let timeInterval: Double = end.timeIntervalSinceDate(start) // <<<<< Difference in seconds (double)

println("Time to evaluate problem \(problemNumber): \(timeInterval) seconds")
return theAnswer
}

Note that using NSdate for timing functions is discouraged: "The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock.".

Accurate timing in iOS

Ok, I have some answers after doing some more tests, so I am sharing it with anyone who is interested.

I've placed a variable to measure time intervals between ticks, inside the play method (the method that actually sends the play message to the AVAudioPlayer object), and as my simple compare-to-external-watch experiment showed, the 60 BPM was too slow - I got these time intervals (in seconds):

1.004915
1.009982
1.010014
1.010013
1.010028
1.010105
1.010095
1.010105

My conclusion was that some overhead time elapses after each 1-second-interval is counted, and that extra time (about 10msec) is accumulated to a noticeable amount after a few tens of seconds --- quite bad for a metronome. So instead of measuring the interval between calls, I decided to measure the total interval from the first call, so that the error won't be accumulated. In other words I've replaced this condition:

while (continuePlaying && ((currentTime0 + [duration doubleValue]) >= currentTime1)

with this condition:

while (continuePlaying && ((_currentTime0 + _cnt * [duration doubleValue]) >= currentTime1 ))

where now _currentTime0 and _cnt are class members (sorry if it's a c++ jargon, I am quite new to Obj-C), the former holds the time stamp of the first call to the method, and the latter is an int counting number of ticks (==function calls). This resulted in the following measured time intervals:

1.003942
0.999754
0.999959
1.000213
0.999974
0.999451
1.000581
0.999470
1.000370
0.999723
1.000244
1.000222
0.999869

and it is evident even without calculating the average, that these values fluctuate around 1.0 second (and the average is close to 1.0 with at least a millisecond of accuracy).

I will be happy to hear more insights regarding what causes the extra time to elapse - 10msec sounds as eternity for a modern CPU - though I am not familiar with the specs of the iPod CPU (it's iPod 4G, and Wikipedia says the CUP is PowerVR SGX GPU 535 @ 200 MHz)

How to benchmark Swift code execution?

If you just want a standalone timing function for a block of code, I use the following Swift helper functions:

func printTimeElapsedWhenRunningCode(title:String, operation:()->()) {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(title): \(timeElapsed) s.")
}

func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
return Double(timeElapsed)
}

The former will log out the time required for a given section of code, with the latter returning that as a float. As an example of the first variant:

printTimeElapsedWhenRunningCode(title:"map()") {
let resultArray1 = randoms.map { pow(sin(CGFloat($0)), 10.0) }
}

will log out something like:

Time elapsed for map(): 0.0617449879646301 s

Be aware that Swift benchmarks will vary heavily based on the level of optimization you select, so this may only be useful for relative comparisons of Swift execution time. Even that may change on a per-beta-version basis.

NSDate: Get precise time independent of device clock?

While Kevin and H2CO3 are completely correct, there are other solutions for the purposes of checking a subscription (which I would hope does not need millisecond accuracy....)

First watch UIApplicationSignificantTimeChangeNotification so that you get notifications of when the time changes suddenly. This will even be delivered to you if you were suspended (though I don't believe you will receive it if you were terminated). This gets called when there is a carrier time update, and I believe it is called when there is manual time update (check). It also is called at local midnight and at DST changes. The point is that it's called pretty often when the time suddenly changes.

Keep track of what time it was when you go into the background. Keep track of what time it is when you come back into the foreground. If time moves radically backwards (more than a day or two), kindly suggest that you would like access to the network to check things. Whenever you check-in with your server, it should tell you what time it thinks it is. You can use that to synchronize the system.

You can similarly keep track of your actual runtime. If it gets wildly out of sync with apparent runtime, then again, request access to the network to sync things up.

I'm certain that attackers would be able to sneak 35 days or whatever out of this system rather than 30, but anyone willing to work that hard will just crack your software and take the check out entirely. The focus here is the uncommitted attacker who is just messing with their clock. And that you can catch pretty well.

You should test this carefully, and be very hesitant to accuse the user of anything. Just connecting to your server should always be enough to get a legitimate user working again.



Related Topics



Leave a reply



Submit