Created
June 19, 2022 14:48
-
-
Save LK-Simon/d1f5979c54a064871e4eea7ff2e4abf8 to your computer and use it in GitHub Desktop.
High Precision "Mach Timer" for MacOS and iOS (Swift implementation)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
enum MachTimerError: Error { | |
case TimebaseInfoError // Only thrown where we cannot initialize the Mach Timer (shouldn't ever happen though) | |
} | |
struct MachTimer { | |
// We need to know whether or not the Timer is still running or Stopped at the point of requesting a Result | |
enum MachTimerState { | |
case NotRunning | |
case Running | |
} | |
// Object to encapsulate a Result and provide basic conversion to Milliseconds and Seconds from NAnoseconds | |
struct MachTimerResult { | |
var nanos: UInt64 // Original Result is in Nanoseconds | |
init(nanos: UInt64) { // We must provide a Nanosecond Result on Initialization | |
self.nanos = nanos | |
} | |
var millis: Double { // Return Result in Milliseconds | |
get { | |
return Double(self.nanos) / 1_000_00 | |
} | |
} | |
var seconds: Double { // Return Result in Seconds | |
get { | |
return Double(self.nanos) / 1_000_000_000 | |
} | |
} | |
} | |
var state: MachTimerState = .NotRunning | |
var startTime: UInt64 = 0 | |
var stopTime: UInt64 = 0 | |
let numer: UInt64 | |
let denom: UInt64 | |
init() throws { | |
var info = mach_timebase_info(numer: 0, denom: 0) | |
let status = mach_timebase_info(&info) | |
if status != KERN_SUCCESS { | |
throw MachTimerError.TimebaseInfoError | |
} | |
self.numer = UInt64(info.numer) | |
self.denom = UInt64(info.denom) | |
} | |
mutating func start(refTime: UInt64? = nil) { | |
let now: UInt64 = mach_absolute_time() // Always get the Reference Time (we might need to rely on it if no refTime is given) | |
let ref: UInt64 = refTime ?? now // Use refTime if given, otherwise fall back to now | |
self.startTime = ref | |
self.state = .Running | |
} | |
mutating func stop(refTime: UInt64? = nil) -> MachTimerResult { | |
let now: UInt64 = mach_absolute_time() // Always get the Reference Time (we might need to rely on it if no refTime is given) | |
let ref: UInt64 = refTime ?? now // Use refTime if given, otherwise fall back to now | |
self.stopTime = ref | |
self.state = .NotRunning | |
return self.result(refTime: self.stopTime) | |
} | |
mutating func result(refTime: UInt64? = nil) -> MachTimerResult { | |
let now: UInt64 = mach_absolute_time() // Always get the Reference Time (we might need to rely on it if no refTime is given) | |
let ref: UInt64 = self.state == .Running ? refTime ?? now : stopTime // If Running, use Now or refTime. If NOT Running, use stopTime | |
return MachTimerResult(nanos: ((ref - self.startTime) * self.numer) / self.denom) | |
} | |
} |
If you need to persist the instance of your MachTimer
without encapsulating all of its referencing code inside of a do
/catch
block, you can do this:
var myTimer: MachTimer? = nil
do {
myTimer = try MachTimer()
}
catch MachTimerError.TimebaseInfoError {
print("Couldn't get Timebase Info!")
}
You can then do nil
checks before using the Timer in the many ways Swift language supports, e.g:
if myTimer {
// Use the Timer
}
or
if !myTimer {
return // Early return pattern
}
myTimer!.start() // Example of asserting that myTimer cannot be nil, and call its start method
or:
myTimer?.start() // Will only invoke the start method if myTimer is not nil
or (for some versions of Swift that don't like the above)
if let actualTimer = myTimer {
actualTimer.start()
// Use the timer referencing actualTimer from here on
}
or (finally) you can do something similar to above, but reusing the same variable name:
if let myTimer = myTimer {
myTimer.start()
// Use the timer referencing myTimer
}
can you please tell me how to call in the view (like in the content view, or you need to make a class???
)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple usage example:
To use
pointInTime
andtotalTime
you can do the following:Don't forget to close the
do
block and handle the possible Error:You can wrap the
do
andcatch
block as close to the initialisation of yourMachTimer
object as you wish. Just remember that it is possible (albeit extremely unlikely) to encounter an error when theMachTimer
attempts to read themach_timebase_info(&info)
which is why we have thethrow
andcatch
in the first place.