-
-
Save LK-Simon/d1f5979c54a064871e4eea7ff2e4abf8 to your computer and use it in GitHub Desktop.
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) | |
} | |
} |
Simple usage example:
do {
var myTimer = try MachTimer()
myTimer.start() // or myTimer.start(mach_absolute_time()) if you care about drift from the call stack
// Do something here
let pointInTime = myTimer.result() // or let pointInTime = myTimer.result(mach_absolute_time()) if you care about drift from the call stack
// Do something else
let totalTime = myTimer.stop() // or let totalTime = myTimer.stop(mach_absolute_time()) if you care about drift from the call stack
To use pointInTime
and totalTime
you can do the following:
print("Point In Time was \(pointInTime.nanos)ns [\(pointInTime.millis)ms]")
print("Total Time was \(totalTime.nanos)ns [\(totalTime.millis)ms]")
Don't forget to close the do
block and handle the possible Error:
}
catch MachTimerError.TimebaseInfoError {
print("Couldn't get Timebase Info!")
exit(1)
}
You can wrap the do
and catch
block as close to the initialisation of your MachTimer
object as you wish. Just remember that it is possible (albeit extremely unlikely) to encounter an error when the MachTimer
attempts to read the mach_timebase_info(&info)
which is why we have the throw
and catch
in the first place.
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???
)
The reason for all of the
now
variables is that we want to get themach_absolute_time()
value as early as physically possible. In this case, the precision of the result is more important than wasting cycles asking for the value in cases where we don't end up using it.The best way to use this timer would be to provide
refTime
parameter values wherever available, and for that value to be frommach_absolute_time()
This timer allows you to call
result = myTimer.result(refTime: mach_absolute_time())
even when the Timer is still running. This is good for situations where you want to take "Checkpoint" times from an overall process using a single Timer.The
stop
function will return the result by default.