views:

46

answers:

2

I've got a problem that only seems to occur on the device, not in the simulator.

My app's animation is started and stopped using these methods:

NSTimer* animationTimer;

-(void)startAnimation
{
    if(animationTimer == nil)
        animationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f/60.0f target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}

-(void)stopAnimation
{
    [animationTimer invalidate];
    animationTimer = nil;
}

In the simulator this works fine and drawView starts being called at 60fps. On the device (testing on iPod Touch), the scheduleTimerWithTimeInterval method doesn't seem to work. Furthermore, [animationTimer invalidate] causes EXC_BAD_ACCESS.

I've spotted an obvious but minor flaw; adding if(animationTimer != nil) to the stopAnimation method will prevent the crash, but doesn't solve the problem of the animation timer not being properly initialised.

Edit: The above doesn't prevent a crash. animationTimer != nil yet calling invalidate causes EXC_BAD_ACCESS.

Should also add, this problem doesn't occur all the time on the device. Maybe 40% of the time.

A: 

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.

Bear in mind that NSTimer does not always fire "on time". Your application works on simulator because of the vast amount of resources available to it - nearly whole processor and RAM avaiable on your Mac. The device, on the other hand, has a limited amount of resources available. This could lead to some sort of a race condition in your application.

The other thing is that you're not retaining the timer (which is a good thing), so the only object that claimed ownership over it is your current run loop. When you call [timer invalidate]; the timer gets released. It's quite possible that your timer was released somewhere else by the [timer invalidate]; function. Trying to invalidate already invalidated timer will result in an error which fits your description.

In overall, look throughout your code for other places where your timer might have been invalidated. Check for race conditions too.

Hope this was helpful, Paul

Pawel
Thanks for the response.I can confirm that those are the only two methods that access the timer, confirmed by changing its' instance name to animTimer and fixing the two build errors.I'm not worried about the timer being accurate as such, and it's not a particularly demanding application. At the moment it's all (60fps) or nothing (drawView just not being called as if the NSTimer never got instantiated).
Tobster
+3  A: 

If animationTimer is declared exactly as you’ve shown, then its initial value is undefined. Messaging a pointer value that doesn’t correspond to an allocated Objective-C object is very likely to give you EXC_BAD_ACCESS. You should explicitly set its initial value to nil, or consider declaring it as a member variable of your class so it will be initialized to 0/nil with the other member variables when the object is allocated.

Pivot
Have implemented animationTimer = nil in the constructor for code corectness, but it hasn't fixed the problem. The [NSTimer scheduleTimerWithTimeInterval:] line is being executed, but this doesn't seem to work every time on the device.
Tobster