iPhone: Using dispatch_after to mimick NSTimer
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 :)
精彩评论