NSTimer repeating too fast

2019-06-09 13:26发布

问题:

The Problem

I am building an app where I am getting real-time data and updating a MKMapView. I get a batch of data every 10 seconds and between data sets from the webs service I am removing older data points while also adding the new ones.

Instead of updating them all at once I want spread out the animation of the new points I get from the data service over that 10 seconds so I create the 'real-time' feel and avoid as many stops and starts as I can.

Everything seems to be working great except the that the NSTimer is always finishing early... way early. It should loop through the new data over 10 seconds but it will typically finish looping through the new data set 4 to 5 seconds earlier then it should.

I have read through a lot of the Apple documentation and StackOverflow questions (below are two good ones for those that may be looking) :)

https://stackoverflow.com/a/18584973 https://developer.apple.com/library/ios/technotes/tn2169/_index.html#//apple_ref/doc/uid/DTS40013172-CH1-TNTAG8000

But it seems like most of the recommendations are made for gaming apps using CADisplayLink (but I am not building a gaming app) or that if you need to use a high performance timer that it should not be used continuously.

My timer does not need to be exact but if I could even get it within .5 seconds that would be great without having to add the overhead of some of the other options I have seen.

As always any thoughts / code / or directions you could point me would be greatly appreciated.

The Code

Once I collect the new data into arrays I create the time interval and start the timer with the code below

addCount = -1;
timerDelay = 10.0/[timerAdditions count];

delayTimer =[NSTimer scheduledTimerWithTimeInterval:timerDelay target:self selector:@selector(delayMethod) userInfo:nil repeats:YES];

That then fires this method that animates through adding and removing the my map annotations.

-(void) delayMethod {

addCount = addCount +1;

if (addCount >= [timerAdditions count]) {

    [timerRemovals removeAllObjects];
    [timerAdditions removeAllObjects];
    addCount = -1;
    [delayTimer invalidate];
    delayTimer = nil;

} else  {


    [myMap addAnnotation:[timerAdditions objectAtIndex:addCount]];
    [myMap removeAnnotation:[timerRemovals objectAtIndex:addCount]animated:YES];

}

}

UPDATE

I tried updating my timer through GCD. And what is odd is that the timing loop works every other dataset. Still do not have it working every tie but for some reason it seems to be tied to resetting the dispatch time or the timer interval.

 -(void) delayMethod {

dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timerDelay); // How long
dispatch_after(delay, dispatch_get_main_queue(), ^(void){

    addCount = addCount +1;

    if (addCount >= [timerAdditions count]) {

        [timerRemovals removeAllObjects];
        [timerAdditions removeAllObjects];
        addCount = -1;
        //[delayTimer invalidate];
        //delayTimer = nil;

    } else  {

        NSLog(@"Delay fired count %i -- additoins %lu",addCount,(unsigned long)[timerAdditions count]);

        [myMap addAnnotation:[timerAdditions objectAtIndex:addCount]];
        [myMap removeAnnotation:[timerRemovals objectAtIndex:addCount]animated:YES];
        [self delayMethod];

    }

});


}

回答1:

I am using timer as like this to do stuff with timer instance for more accurate result.

- (void)createTimer {
    // start timer
    if(gameTimer == nil)
        gameTimer = [NSTimer timerWithTimeInterval:1.00 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES] ;
    [[NSRunLoop currentRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
    timeCount = 725; // instance variable or you can set it as your need
}
- (void)timerFired:(NSTimer *)timer {
    // update label
    if(timeCount == 0){
        [self timerExpired];
    } else {
        timeCount--;
        if(timeCount == 0) {
            // display correct dialog with button
            [timer invalidate];
            [self timerExpired];
        }
    }
    //do your stuff here for particular time.
}
- (void) timerExpired {
    // display an alert or something when the timer expires.
    NSLog(@"Your time is over");
    [gameTimer invalidate];
    gameTimer = nil;

    //do your stuff for completion of time.
}

in this take time interval you want. and also Increment decrement stuff as you require. And do stuff with timer fired and completed. In your situation if you don not want to expire timer than its ok. never invalidate timer instance and use only timer fired event to do stuff.



回答2:

I went down a slightly different path thanks to another SO question I have referenced below. Basically by combining the two timers into one, setting that one timer to the fastest time interval I would need and managing the methods I need to at the changing intervals within the method called by the timer I have solved the problem I was seeing.

https://stackoverflow.com/a/25087473/2939977

timeAdder = (10.0/[timerAdditions count]);
timeCountAnimate = timeAdder;
[NSTimer scheduledTimerWithTimeInterval:0.11 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];




- (void)timerFired {

timeCount += 0.111;

if (timeCount > 10.0) {

    // call a method to start a new fetch
    timeCount = 0.0;
    timeCountAnimate =0.0;
    [self timerTest];

}

if (timeCount > timeCountAnimate) {

    timeCountAnimate += timeAdder;
     [self delayMethod];

}