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?
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.Out of the
NSTimer
docs (emphasis mine):The best way to remedy the issue, if you are prepared to also optimize your drawing -- is to use
CADisplayLink
, as others have mentioned. TheCADisplayLink
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).
NSTimers are not guaranteed to run at exactly the rate you asked for. If you're developing a game, you should investigate
CADisplayLink
, which implements roughly the same interface as NSTimer but is fired at every screen redraw.You set up a display link just like your timer, only you don't have to specify a refresh period.
Above all you should measure the amount of time that has passed since the last iteration. Don't rely on it being exactly 1/30th or 1/60th of a second. If you have code like this
change it to this
If you need a high-precision timer for a reason other than making a game, see Technical Note TN2169.
As Hot Licks suggested, 30 Hz is reasonably fast, so depending on what you're doing inside
moveEverything
, you might simply be overwhelming your phone's processor. You don't showmoveEverything
, so we can't really comment on what's inside ... but, you may need to work at making that method more efficient, or slowing down the rate at which your timer fires.Also, it's possible that your
NSTimer
is simply not firing at times, because the timer has not been added for all the right run loop modes.For example, when a scroll view scrolls (a performance intensive operation), the UI is in tracking mode. By default, the way you've created your timer, the timer will not fire when in tracking mode. You can change this by using something like this: