I wanted to start a discussion about the timers and an API that exposes the capabilities of the system timers. A couple of things I noticed were that the timers use CADisplayLink on the main thread and also listen to the UIApplication state notifications. To my knowledge this works well for timers that are coupled to the UI (requestAnimationFrame comes to mind, there may be other uses). I’d like to discuss timers that do other work e.g. interact with the event loop or schedule background tasks, and how they co-interact.
Event loop timers seem to fall into two categories – those that run at the end of the current loop and those that run right after. This is like process.nextTick and setImmediate. These aren’t too hard to implement though there are a couple of gotchas like warning if a process.nextTick handler endlessly schedules itself before blowing the stack. I would also expect both of these timers to run even if the app is inactive or backgrounded (e.g. say you get a network response while the app is backgrounded and schedule some work to run soon after – it should run in the background).
Background task timers (setTimeout, setInterval) raise a couple of questions like the semantics of a zero-ms timeout. I would also propose that these run in the background as well to address the use of running code that does not need the app to be foregrounded. And, if the app is backgrounded there is little harm in running UI code anyway — in cases where it does matter, the timer handler can check the UIApplication state.
Background task timers that repeat are also good candidates for energy optimization. One thing that OS X systems do is coalesce timers where possible, partly aided by the programmer providing a tolerance value. If many running apps schedule timers that can overlap within their respective tolerance windows, the system may wake the CPU just once to run all of those tasks together.
There is also a question of the ordering of all of these timers. My strawman proposal is:
- End of event loop: nextTick
- ASAP after end of prior event loop: setImmediate, followed by setTimeout(0)
- A short while later (using NSTimer): setTimeout(n + [0-tolerance]) up until the next frame begins
- Start of next frame (CADisplayLink): requestAnimationFrame. However, this will wait till later if the app is foregrounded or inactive. Also I believe CADisplayLink may not fire if momentum scrolling is active (not verified).
- Some time later (again using NSTimer): setTimeout(n + [0-tolerance]) where n is big enough that a frame has elapsed
Additionally, setTimeout(delay = 9, threshold = 2) should run before setTimeout(delay = 10, threshold = 0), and two setTimeouts with the same delay should run in the order that they were scheduled even if they have different thresholds. But to keep things simple at first I would recommend we go with whatever the system timer (NSTimer) does. There’s an argument to be made that the timer handlers should use promises or semaphores, etc if they need to coordinate and not rely on the timer system.