Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xcode / Objective-C: Why is NSTimer sometimes slow/choppy?

I am developing an iPhone game and I have an NSTimer that animates all of the objects on the screen:

EverythingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(moveEverything) userInfo:nil repeats:YES];

Most of the time it runs very smoothly, but sometimes I see things move slower or choppy. I have pause and resume functions that stop and start the timers respectively. When I pause then unpause, it seems to fix the choppiness.

Any ideas to why this is happening? or How can i fix it?

like image 546
Anthony Mae Avatar asked Jan 12 '23 11:01

Anthony Mae


1 Answers

Any ideas to why this is happening?

The short answer is that in your case, you are performing actions (animations) based on an unsynchronized push model, and the mechanism you are using is not suited for the task you are performing.

Additional notes:

  • NSTimer has a low resolution.
  • Your work is performed on the main run loop. The timer may not fire when you expect it to because much time may be spent while work on that thread is underway, which blocks your timer from firing. Other system activities or even threads in your process can make this variation even greater.
  • Unsynchronized updates can result in a lot of unnecessary work, or choppiness because the updates you perform in your callback(s) are posted at some time after they occur. This adds even more variation to the timing accuracy of your updates. It's easy to end up dropping frames or perform significant amounts of unnecessary work when the updates are not synchronized. This cost may not be evident until after your drawing is optimized.

Out of the NSTimer docs (emphasis mine):

A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.

The best way to remedy the issue, if you are prepared to also optimize your drawing -- is to use CADisplayLink, as others have mentioned. The CADisplayLink is a special 'timer' on iOS which performs your callback message at a (capable) division of the screen refresh rate. This allows you to synchronize your animation updates with the screen updates. This callback is performed on the main thread. Note: This facility is not so convenient on OS X, where multiple displays may exist (CVDisplayLink).

So, you can start by creating a display link and in its callback, perform work which would deal with your animations and drawing related tasks (e.g. perform any necessary -setNeedsDisplayInRect: updates). Make sure your work is very quick, and that your rendering is also quick -- then you should be able achieve a high frame rate. Avoid slow operations in this callback (e.g. file io and network requests).

One final note: I tend to cluster my timer sync callbacks into one Meta-Callback, rather than installing many timers on run loops (e.g. running at various frequencies). If your implementations can quickly determine which updates to do at the moment, then this could significantly reduce the number of timers you install (to exactly one).

like image 112
justin Avatar answered Jan 24 '23 12:01

justin