开发者

iPhone: Using dispatch_after to mimick NSTimer

开发者 https://www.devze.com 2023-02-04 22:23 出处:网络
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, b

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 *)scheduledTimerWith开发者_运维技巧TimeInterval:(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


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


You could also simply "pause" a vanilla NSTimer by setting the fireDate to [NSDate distantFuture]. When your app comes back into the foreground, you could then simply resume the normal schedule.


I don't know much myself about blocks and background tasks but I would think of a different technique to "pause" your timer:

I would suggest to overwrite applicationDidEnterBackground and store the time remaining for the timer (based on NSTimer's fireDate), then when the app is back to foreground and applicationWillEnterForeground is fired, invalidate and re-calculate your timers based on the timestamp you saved before.

I haven't tested this solution, but seems it should be working

Hope this helps :)

0

精彩评论

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