Stop an NSRunLoop from a timer

Elbimio picture Elbimio · Jul 8, 2011 · Viewed 12.1k times · Source

I've made a RunLoop with a timer that updates a label that displays a countdown. I need the RunLoop to stop once the countdown reaches zero, for the case where the the timer finishes normally I could just use runUntilDate, with the date being the current date + the time on the countdown. The problem is when the user cancels the countdown from a button before it's finished. I don't know how to tell the RunLoop to stop from the cancel button action. Here's the code for the RunLoop:

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
                            [self methodSignatureForSelector:@selector(updateCountdownLabel:)]];
[invocation setTarget:self];
[invocation setSelector:@selector(updateCountdownLabel:)];
[[NSRunLoop mainRunLoop] addTimer:[NSTimer timerWithTimeInterval:1 invocation:invocation repeats:YES] forMode:NSRunLoopCommonModes];

The method just tells the label to reduce by 1 in each loop.

I could tell the cancel button to change the label to zero, and have the run loop selector check if the value is zero, but could the RunLoop's own selector tell it to stop?

cancelPerformSelector:target:argument:

cancelPerformSelectorsWithTarget:

These are the closest I've found but they don't seem to work from inside the RunLoops own selector, or at least not in any way I've tried them.

Basically I need to have the button tell the RunLoop to stop, or somehow stop the RunLoop from it's own selector.

Thanks.

Answer

Rob Keniger picture Rob Keniger · Jul 8, 2011

You haven't made a run loop, you've scheduled a timer to begin on the main run loop.

What you should do is store the NSTimer object that you create as an instance variable before scheduling the timer on the run loop.

In your updateCountdownLabel: method, once your end condition has been satisfied just call -invalidate on your timer instance. This will remove the timer from the run loop, and because you never retained it, it will be released.

I've updated the methods to use a selector-based NSTimer rather than your NSInvocation-based one. This means that the callback method signature is defined as you are expecting. It also avoids the need to store the NSTimer object in an ivar:

- (void)startCountDown
{

    NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateCountdownLabel:) userInfo:nil repeats:YES]
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}


- (void)updateCountdownLabel:(NSTImer*)timer
{
    if(thingsAreAllDone)
    {
        [timer invalidate];
    }
}