iPhone: Using dispatch_after to mimick NSTimer

Joseph Tura picture Joseph Tura · Jan 17, 2011 · Viewed 9.1k times · Source

Don't know a whole lot about blocks. How would you go about mimicking a repeating NSTimer with dispatch_after()? My problem is that I want to "pause" a timer when the app moves to the background, but subclassing NSTimer does not seem to work.

I tried something which seems to work. I cannot judge its performance implications or whether it could be greatly optimized. Any input is welcome.

#import "TimerWithPause.h"


@implementation TimerWithPause

@synthesize timeInterval;
@synthesize userInfo;
@synthesize invalid;
@synthesize invocation;

+ (TimerWithPause *)scheduledTimerWithTimeInterval:(NSTimeInterval)aTimeInterval target:(id)aTarget selector:(SEL)aSelector userInfo:(id)aUserInfo repeats:(BOOL)aTimerRepeats {

    TimerWithPause *timer = [[[TimerWithPause alloc] init] autorelease];

    timer.timeInterval  = aTimeInterval;

    NSMethodSignature *signature = [[aTarget class] instanceMethodSignatureForSelector:aSelector];
    NSInvocation *aInvocation = [NSInvocation invocationWithMethodSignature:signature];
    [aInvocation setSelector:aSelector];
    [aInvocation setTarget:aTarget];
    [aInvocation setArgument:&timer atIndex:2];
    timer.invocation = aInvocation;

    timer.userInfo      = aUserInfo;

    if (!aTimerRepeats) {
        timer.invalid = YES;
    }

    [timer fireAfterDelay];

    return timer;
}

- (void)fireAfterDelay {

    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, self.timeInterval * NSEC_PER_SEC);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_after(delay, queue, ^{

        [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:NO];

        if (!invalid) {         
            [self fireAfterDelay];
        }
    });
}

- (void)invalidate {
    invalid = YES;

    [invocation release];
    invocation = nil;

    [userInfo release];
    userInfo = nil;
}

- (void)dealloc {

    [self invalidate];

    [super dealloc];
}

@end

Answer

pho0 picture pho0 · Apr 26, 2011

So for timers that you want to fire a block, you would a dispatch source. There is a great example on the ADC website that I've used many times in lieu of NSTimer in my apps:

http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html%23//apple_ref/doc/uid/TP40008091-CH103-SW2