Accuracy of NSTimer

I am trying to use NSTimer to create a Stop-watch style timer that increments every 0.1 seconds, but it seems to be running too fast sometimes ..

This is how I've done it:

Timer =[NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:@selector(updateTimeLabel) userInfo:nil repeats: YES];

and then:

-(void)updateTimeLabel
{
    maxTime=maxTime+0.1;
    timerLabel.text =[NSString stringWithFormat:@"%.1f Seconds",maxTime];
}

This will display the value of the timer in the Label, and I can later utilize maxTime as the time when the Timer is stopped ...

THe problem is that it runs very inaccurately.

Is there a method where I can make sure that NSTimer fires strictly every 0.1 seconds accurately ? I know that NSTimer isn't accurate , and I'm asking for a tweak to make it accurate.

THanks


Solution 1:

According to the NSTimer documentation, it is not meant to be accurate.

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.

You may want to use the dispatch_after function from GCD, which is suggested by the official documentation for this exact purpose (creating a timer).

If you want to perform a block once after a specified time interval, you can use the dispatch_after or dispatch_after_f function.


By the way, I agree with Caleb's answer. You probably are going to solve your problems if you don't accumulate error like your doing right now. If you store the start date and recalculate the time at every iteration using the -timeIntervalSince: method, you're gonna end up with an accurate UI update, regardless of the timer precision.

Solution 2:

Here's a class you can use to do what you want:

@interface StopWatch()
@property ( nonatomic, strong ) NSTimer * displayTimer ;
@property ( nonatomic ) CFAbsoluteTime startTime ;
@end

@implementation StopWatch

-(void)dealloc
{
    [ self.displayTimer invalidate ] ;
}

-(void)startTimer
{
    self.startTime = CFAbsoluteTimeGetCurrent() ;
    self.displayTimer = [ NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector( timerFired: ) userInfo:nil repeats:YES ] ;
}

-(void)stopTimer
{
    [ self.displayTimer invalidate ] ;
    self.displayTimer = nil ;

    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
    [ self updateDisplay:elapsedTime ] ;
}

-(void)timerFired:(NSTimer*)timer
{
    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
    [ self updateDisplay:elapsedTime ] ;
}

-(void)updateDisplay:(CFAbsoluteTime)elapsedTime
{
    // update your label here
}

@end

The key points are:

  1. do your timing by saving the system time when the stop watch is started into a variable.
  2. when the the stop watch is stopped, calculate the elapsed time by subtracting the stop watch start time from the current time
  3. update your display using your timer. It doesn't matter if your timer is accurate or not for this. If you are trying to guarantee display updates at least every 0.1s, you can try setting your timer interval to 1/2 the minimum update time (0.05s).

Solution 3:

maxTime=maxTime+0.1;

This is the wrong way to go. You don't want to use a timer to accumulate the elapsed time because you'll be accumulating error along with it. Use the timer to periodically trigger a method that calculates the elapsed time using NSDate, and then updates the display. So, change your code to do something instead:

maxTime = [[NSDate date] timeIntervalSince:startDate];

Solution 4:

NSTimer is not guaranteed to be accurate, although in practice it usually is (if you're not doing anything else on your main thread...). However, it's perfectly reasonable for updating a display... just don't use the callback to calculate your timer. Save the current time when you start your timer, and get the difference between now and when you started every time the timer fires. Then it doesn't really matter how accurately NSTimer is firing, it only impacts how many times a second your on screen display updates.