Skip to content

Instantly share code, notes, and snippets.

@LK-Simon
Created June 19, 2022 14:48
Show Gist options
  • Select an option

  • Save LK-Simon/d1f5979c54a064871e4eea7ff2e4abf8 to your computer and use it in GitHub Desktop.

Select an option

Save LK-Simon/d1f5979c54a064871e4eea7ff2e4abf8 to your computer and use it in GitHub Desktop.
High Precision "Mach Timer" for MacOS and iOS (Swift implementation)
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)
}
}
@LK-Simon
Copy link
Author

LK-Simon commented Jun 19, 2022

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
}

@NilaxanN
Copy link

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