-
-
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 stackTo 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 methodor:
myTimer?.start() // Will only invoke the start method if myTimer is not nilor (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
nowvariables 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
refTimeparameter 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
stopfunction will return the result by default.