Search code examples
iosnstimer

Why timer invoke its block so quickly sometimes


I create a timer and invoke its block every 5 second. Then I make application to enter background and enter foreground after a while. But it could invoke the block quickly sometimes.

let _ = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { (timer) in
        print("--------")
   }

When I enter foreground the interval of first printing and second printing may less than a second sometimes. Is time interval invalid in this case?


Solution

  • To understand the behavior, you need to understand how NSTimer and RunLoop works. In simple terms, a RunLoop would check if the Timer should fire, if yes it would notify the Timer to fire the selector, else it won't. Now, since you are on the background, your RunLoop is not checking for events so it won't be able to notify the Timer. But once it goes to foreground, it would see that it would need to notify the Timer even if it passed the fireDate.

    TimeLine Diagram:

    Let A(5th second) and B(10th second) be timer fire events. Scheduled on a timer Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true)

    C is entered background (0 seconds)

    D be coming back to foreground (9th second, between A and B).

    -----> A ------> B

    C--------->D

    Explanation:

    On C, the RunLoop would be paused. Therefore Event A is not able to be processed until the RunLoop has resumed processing, which is on Event D. Upon Event D, it will see that Event A should fire so it would notify the Timer. After a second, the RunLoop would see that Event B has happened so it would notify the Timer again. This scenario explains why your events are printing in a second's interval. It is just the delayed event handling that makes it seem that it fired earlier, when in reality it was processed late.

    Apple Doc:

    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.

    resources: What is an NSTimer's behavior when the app is backgrounded?, NSRunLoop and Timer Docs

    Suggestions:

    Stop your timer once app goes background, but store the fireDate. Once coming back to foreground, check if fireDate is past Date(). Then create a new Timer to handle events while on foreground.