Swift 3 precise Timer repeating (iOS 9) - ios

I've got a precise timer that needs to be update every 40ms (most precisely than possible). On iOS10 it's good (I use the new scheduleRepeating method) but on iOS9 I need to use the old way (scheduledTimer) and it's pretty laggy (sometimes 24ms, sometimes its 72...), so my hardware interface and visual effects and lagging.
Any suggestion?
func launchTimer() {
if #available(iOS 10.0, *) {
startTickTimer()
} else {
let timerQueue = DispatchQueue(label: "my.queue.tickTimer", attributes: .concurrent)
self.swiftTimer = Timer.scheduledTimer(timeInterval: period, target: self, selector: #selector(executeTimer), userInfo: nil, repeats: true)
timerQueue.async {
RunLoop.current.add(self.swiftTimer!, forMode: RunLoopMode.defaultRunLoopMode)
}
}
}
static func startTickTimer() {
let queue = DispatchQueue(label: "my.queue.tickTimer", attributes: .concurrent)
DMXTimer.tickTimer?.cancel()
DMXTimer.tickTimer = DispatchSource.makeTimerSource(queue: queue)
DMXTimer.tickTimer?.scheduleRepeating(deadline: .now(), interval: .milliseconds(40), leeway: .seconds(1))
DMXTimer.tickTimer?.setEventHandler {
executeTimer()
}
DMXTimer.tickTimer?.resume()
}
static func executeTimer() {
print("hello moto")
}

From Apple doc about NSTimer:
A timer is not a real-time mechanism. If a timer’s firing time occurs
during a long run loop callout or while the run loop is in a mode that
isn't monitoring the timer, the timer doesn't fire until the next time
the run loop checks the timer. Therefore, the actual time at which a
timer fires can be significantly later. See also Timer Tolerance.
If you want a very precise timer you can check an implementation from Apple Tech Note and adapt in Swift or use a CADisplayLink for display updates.

I would suggest to use NSThread and loop inside instead of timer. It is more complicated but, imho, it has more settings and flexible behaviour

Related

Proper way to run CADisplayLink on async background thread?

What is the proper way to make CADisplayLink callback functions run on a background thread? I'm creating the Display Link with:
let displayLink = CADisplayLink(target: self, selector: #selector(self.renderBackground))
if let displayLink = displayLink {
displayLink.preferredFramesPerSecond = 30
DispatchQueue.main.async {
displayLink.add(to: .current, forMode: .common)
}
}
This works, but when I add a breakpoint to the renderBackground function, it shows it is on the Main thread:
It appears to be on the main thread?
A CADisplayLink is intended to provide callbacks synchronized with the screen refresh, which takes place on the main thread. It doesn't make sense to synchronize an event that occurs on the main thread with calls on another thread.
I suspect there is no way to do this.

NSTimer not firing after home button is pressed

II have an NSTimer set to fire each second. Using the Simulator this will call the eachSecond() method correctly after I navigate away from the app by pressing the home button. However, on a real iPhone 6, once I navigate away eachSeconds is not called again until the app is open again.
Why might this behave differently on a real device? Is there anyway to ensure it will fire each second in this use case?
Its a fitness app, so needs to record duration when the phone locks or user navigates away momentarily.
Thanks,
lazy var timer = NSTimer()
fun init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(RunViewController.eachSecond(_:)), userInfo: nil, repeats: true)
}
func eachSecond() {
// update UI etc.
}
An NSTimer will not fire when your app is suspended. Background execution is different when running under XCode; background execution limits do not apply.
In order to determine how much time has elapsed while your app is suspended, you can store a timestamp in applicationDidEnterBackground and calculate the elapsed time based on the difference between the saved timestamp and current time in applicationWillEnterForeground
You need to add the timer to the main run loop.
func init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(RunViewController.eachSecond(_:)), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}

Subsequent WKInterfaceTime / NSTimer events with array of NSTimeInterval values

I am trying to run subsequent Timer events with WKInterfaceTimer & NSTimer, the problem is that I cannot figure out a way to make more than two subsequent calls with one NSTimer object. Basically, I would like run timer to complete then fire up the next.
Here's some sample code that hopefully explains my idea a little better....
1) I am firing off the first timer in awakeWithContext:
func initalTimer() {
let timer1String = NSMutableAttributedString(string: "Lap1")
runStatusLabel.setAttributedText(timerString)
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
runTimer.setDate(NSDate(timeIntervalSinceNow: duration))
runTimer.start()
}
NOTE: Everything works great at this point, then the tiemrDone function is called where I then fire off another timed event.
2)
func timerDone() {
//print("Done")
elapsedTime = 0.0
myTimer!.invalidate()
startTime = NSDate()
timeRunning = false
// Call second timed event
timer2() // just another NSTimer / WKInterfaceTimer function
}
"Stacking" the functions with a completionHandler does not seem to help OR most likely I am doing something wrong...
func execute_Timers(timeInterval: NSTimeInterval, completionHandler: (success: Bool, error: String?) -> Void ) -> Int {
// Code below never gets executed
}
I haven't tested this, and it is just a guess: When your timerDone() method is called, you invalidate the timer. Therefore it doesn't "complete," so your completion routine isn't called. When your timer completes, it gets invalidated anyway, so the call should not be needed. Try removing:
myTimer!.invalidate()
and see what happens.
Thanks for the reply, and you are quite correct - I do not need to call myTimer!.invalidate(). The solution that worked for me was to have different timerDone methods and conditionaly call the next time method.
-Paul

Is it safe to schedule NSTimer on Main thread and keep running the timer when the app is in background?

I have scheduled and NSTimer on main thread.
func startTimer(){
MYClass.runUIBlock { () -> Void in
self.invalidateRecordTimer()
self.updateTimerForInactiveRecord(self.fiveMinutesInSeconds)
}
}
private func updateTimerForInactiveRecord(timeToRecord:Double){
recordTimer = NSTimer(timeInterval: timeToRecord, target: self, selector:"collectData", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(recordTimer!, forMode: NSRunLoopCommonModes)
}
The timer keeps on running even if I am in background. Wanted to ask, is it safe to keep the timer ON without invalidating it before the background timer gets over ? Or do I need to check for available background time and invalidate the timer before background timer gets over ?
At present, the timer keeps on running indefinitely in the background.
An NSTimer can run only a max of 10 minutes when in background. Apple has done this purposefully to avoid battery drainage. If you are not supposed to do important activity when in background then as a good citizen it is advised to stop the timer when going in background.

In Swift, what is the easiest way to call a method in 5 seconds (non-blocking)?

I have this, but seems overly complicated:
var connectionTimer = MZTimerLabel(timerType: MZTimerLabelTypeTimer)
connectionTimer.delegate = self
connectionTimer.tag = 0
connectionTimer.setCountDownTime(5)
connectionTimer.start()
func timerLabel(timerLabel: MZTimerLabel!, finshedCountDownTimerWithTime countTime: NSTimeInterval) {
self.callFinished()
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue()) {
self.callFinished()
}
This is part of the awesome and versatile library Grand Central Dispatch, which will allow you to perform not just delays but all sorts of concurrent operations you will almost certainly have threading bugs in! Luckily, since in your case you are calling back to the main queue, none of your code will execute concurrently, so there's nothing to worry about in this case.
Try this
let delayTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("delayed"), userInfo: nil, repeats: false)
func delay() { println ("hi") }
It will call delay five seconds later.

Resources