开发者

NSTimer inaccurate?

开发者 https://www.devze.com 2023-04-11 00:53 出处:网络
I\'m using an NSTimer to update a label that shows the current time each minute. I fire the timer at a full minute date (e.g. 2:23:00 instead of 2:22:17). The problem is that NSTimer seems to work ina

I'm using an NSTimer to update a label that shows the current time each minute. I fire the timer at a full minute date (e.g. 2:23:00 instead of 2:22:17). The problem is that NSTimer seems to work inaccurately, which means it sometimes fires too early (which results in (e.g.) 2:23 PM when it is really开发者_高级运维 2:24 PM).

Is there any fix / workaround?

EDIT: Here's how I start the timer:

NSTimer *clockTimer = [[NSTimer alloc] initWithFireDate:[NSDate nextFullMinuteDate]
                                               interval:60.0
                                                 target:self
                                               selector:@selector(updateClocks:)
                                               userInfo:nil
                                                repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:clockTimer
                             forMode:NSDefaultRunLoopMode];
[clockTimer release];

Btw, I have tried adding a few milliseconds to the fire date, this seems to help but it looks like finding an appropriate interval to add is hit-and-miss.

EDIT 2: Ok, so I manually set my timer to fire 0.05 seconds after a full minute and then again after 60.0 seconds. Now here's what I logged in the called method:

0.048728
0.047153
0.046484
0.044883
0.043103

As you can see, NSTimer doesn't actually use a 60 second time interval, but a slightly shorter one.


From the NSTimer docs:

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.

This means that an NSTimer is horribly inaccurate.

If you programming a "stopwatch," you could use the NSTimer to display the results to the user while an NIRTimer(one of my favorite classes) keeps track of time. This is outlined below:

NIRTimer *nirTimer;
NSTimer *timer;

- (void)start {
    nirTimer = [[NIRTimer alloc]init];
    timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
    [nirTimer start];

- (void)timerFired:(NSTimer *)timer {
    [self displayTimeToUser:[nirTimer seconds]];
}

In the code above, the NSTimer may not be accurate, but the precise time is kept by the NIRTimer, which is displayed every 0.01 seconds to the user.

If you actually need specific code run at an exact time, the best solution I've found is to use an NSThread and usleep():

BOOL timerRunning;

- (void)start {
     timerRunning = YES;
     [NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil]; //make a new thread
}

- (void)timer {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; //Make an autorelease pool since we're in a new thread
    NIRTimer *timer = [[NIRTimer alloc]init]; //Make an NIRTimer
    while (timerRunning) {
        [timer reset];
        [timer start];
        [self doSomething];
        usleep(10000 - [timer microseconds]); //Wait for .01 seconds which is 10000 microseconds (a microsecond is 1/1000000th of a second) - the time it took to accomplish whatever we were doing
    }
    //Clean up
    [timer release];
    [pool release];
}

In this case, set timerRunning to NO to stop the timer. You may also want to use [self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO] instead of [self doSomething] if what you're trying to do isn't thread safe.

For more on the wonderful world of threading, take a look at the Threading Programming Guide.


I assume that you're starting with the current time, then each time your NSTimer goes off you add one minute to that original time and then display it. What you should do with your timer is: each time the timer goes off, re-read the current time and just display that.


You should set your timer to fire each second instead of each minute. But don't update your label each second. Just compare if the new minutes in the current time is different then the minutes in the last time you updated the label and if so update the label.


Well, I just wrote my own little NSTimer wrapper, called MinuteTimer to "solve" the problem. I've uploaded it on pastebin. The idea is to reset the timer whenever it has fired too quickly so often that the date risks being displayed incorrectly.

Since this is duct-tape programming, I'm still eager to hear other answers!

0

精彩评论

暂无评论...
验证码 换一张
取 消