Reset an NSTimer's firing time to be from now instead of the last fire

前端 未结 5 559
面向向阳花
面向向阳花 2021-01-02 13:33

I have a NSTimer that fires with an interval of 3 seconds to decrease a value. When I do an action that increases that value, I want to restart the timer to cou

相关标签:
5条回答
  • 2021-01-02 13:41

    I've done a little testing, and it turns out that resetting the fireDate is about four times faster than invalidating and re-creating the timer. First, I create a timer which calls the method doNothing:

    if (!testTimer) {
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3.0
                                                          target:self
                                                        selector:@selector(doNothing:)
                                                        userInfo:nil
                                                         repeats:NO];
        testTimer   = timer;
    }
    

    Here is the test code:

    - (void) testInvalidatingTimer {
        for (int n = 0; n < 10000; n++) {
            [testTimer invalidate];
            testTimer = nil;
    
            NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3.0
                                                              target:self
                                                            selector:@selector(doNothing:)
                                                            userInfo:nil
                                                             repeats:NO];
            testTimer   = timer;
        }
    }
    
    - (void) testResettingTimer {
        for (int n = 0; n < 10000; n++) {
            if ([testTimer isValid]) {
                testTimer.fireDate = [NSDate dateWithTimeIntervalSinceNow:3.0];
            }
        }
    }
    

    Running that on an iPad Air yields 0.198173 s for invalidatingTimer and 0.044207 s for resettingTimer. If performance is your target, I recommend to reset the fireDate. It is also quite a bit less coding effort.

    0 讨论(0)
  • 2021-01-02 13:45

    The setFireDate: docs mention that changing the fire date is relatively expensive, suggesting that it may be better to destroy and recreate the timer unless you're doing so a lot. For the sake of argument, however, I whipped up this category a little while ago. I prefer this because it encapsulates the date adjustment behavior; the timer itself handles it, rather than its owner/controller. I don't have any performance data on this, though.

    @implementation NSTimer (WSSAdjustingFireDate)
    
    - (void)WSSFireAdjustingFireDate
    {
        [self fire];
    
        [self WSSSkipNextFireAdjustingFireDate];
    }
    
    - (void)WSSSkipNextFireAdjustingFireDate
    {
        CFRunLoopTimerRef cfSelf = (__bridge CFRunLoopTimerRef)self;
        CFTimeInterval delay = CFRunLoopTimerGetInterval(cfSelf);
        CFRunLoopTimerSetNextFireDate(cfSelf, CFAbsoluteTimeGetCurrent() + delay);
    }
    
    @end
    

    I've used the Core Foundation functions just to avoid creating an NSDate. Premature optimization? Please pass the salt.

    0 讨论(0)
  • 2021-01-02 13:49

    Yes, invalidating and recreating the timer will work. It is essentially what you are wanting to do if you are not increasing your value: reset and start over.

    0 讨论(0)
  • 2021-01-02 13:53

    Yes, you can invalidate it. And create it again.

    You can also use:

    - (void) myTimedTask {
        // other stuff this task needs to do
    
        // change the variable varyingDelay to be 1s or 3s or...  This can be an instance variable or global that is changed by other parts of your app
        // weStillWantTimer is also a similar variable that your app uses to stop this recurring task
    
    if (weStillWantTimer)
           [self performSelector:@selector(myTimedTask:) withObject:gameColor afterDelay:varyingDelay];
    
     }
    

    You call myTimedTask to start the recurring task. Once it is started, you can change the delay with varyingDelay or stop it with weStillWantTimer.

    0 讨论(0)
  • 2021-01-02 13:57

    Invalidate the timer and recreate it. However, make sure you don't invalidate and release the timer unless you are sure you need to since the run loop retains timers until they are invalidated and then releases them itself.

    In my opinion mixing -performSelector code with timer code leads to multiple execution of the target methods, so I'd stay away from that.

    0 讨论(0)
提交回复
热议问题