iOS Swift Timer sometimes fires much later than expected

57 views Asked by At

Creating a 10 minute timer. It always works for me on multiple phones and iOS versions. Timer fires at 10 minutes. I have a user report that their timer fires at much longer interval, 14-18 minutes. What could cause this behavior? This is running as part of a regular iOS UIKit based app.

User with the issue is running iOS 17.3.1 on iPhone 14 Pro.

The call to startLockout is being performed on the main thread.

Code generally as follows:

class Manager {
    static let shared = Manager()

    var timers: Set<Timer> = []

    func startLockout() {
        let timerLengthInSeconds: Double  = 60 * 10
        let timer = Timer.scheduledTimer(timeInterval: timerLengthInSeconds,target: self,
                                     selector:#selector(resetLockout),
                                     userInfo: nil,
                                      repeats: false)
        timers.insert(timer)
    }

    @objc func resetLockout(for: Any) {
         print("Time: \(Date())")
    }
}
1

There are 1 answers

5
Rob On

While there is some variance that can take place as a result of “timer coalescing” (which can be fixed by using a “strict” gcd timer, or changing the tolerance of the Timer), that is unlikely the problem here.

As Joakim suggested, if the timer is really firing minutes later, that’s more than can generally be accounted for by mere timer coalescing. The more likely problem is that the app was suspended when the user left it to do something else. When the app is suspended, no timers will fire until the user relaunches the app. (And you cannot keep an app running perpetually in the background; when the user leaves the app, execution will be suspended.)

As a word of caution, do not test this “what happens when the user leaves the app” scenario while debugging with Xcode. The Xcode debugger can artificially keep the app running in the background even if the user leaves the app on the device. Do your tests while launching the app from the device itself, not running it attached to the debugger.

Assuming this is the case, if you want the user to be notified even if they left the app and the app was suspended, you would reach for the UserNotifications framework. Your app may be suspended, but the OS will present a user notification at the appointed time.

So, let’s say that a user started a 10 minute timer and left the app. Rather than relying on the Timer (which will not fire when the app is suspended), you would schedule a user notification for the appointed time. That way, even if your app is not active, the OS will present a user notification at the appointed time. The user will either dismiss the alert, or tap on it to activate the app. But either way, they’ll know that 10 minutes have passed.

See WWDC 2021 video Send communication and Time Sensitive notifications.