I keep having troubles with my NSTimers and background selectors. It is driving me nuts and takes a very long time to try out each tweak. To preserve my sanity, and the sani
For as long as you depend on using scheduledTimerWithTimeInterval:...
you cannot achieve what you want:
The timer will always be tied to the run-loop of the calling thread.
If there is no run-loop associated with that thread by the time of that message's invocation, there surely is one when the method returns as -[NSRunLoop currentRunLoop]
creates a run-loop if necessary.
What you can do, if you don't like the other APIs for creation of a timer, is providing a category on NSTimer
, which takes care of all the scheduling and so forth and that you can reuse in other projects.
Here is an example of what such a category might look like:
#pragma mark - setting up a timer:
+ (NSTimer *)yourPrefix_mainLoopScheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSTimer *timer = [self yourPrefix_timerWithTimeInterval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
void (^scheduler)() = ^{
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
};
if ([NSThread isMainThread]) {
scheduler();
} else {
// you should really be able to rely on the fact, that the timer is ready to roll, when this method returns
dispatch_sync(dispatch_get_main_queue(), scheduler);
}
return timer;
}
// this is just a convenience for the times where you actually want an _unscheduled_ timer
+ (NSTimer *)yourPrefix_timerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:interval];
NSTimer *timer = [[self alloc] initWithFireDate:fireDate interval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
return [timer autorelease];
}
#pragma mark - tearing it down:
- (void)yourPrefix_invalidateMainLoopTimer
{
[self yourPrefix_invalidateMainLoopTimerAsynchronous:NO];
}
- (void)yourPrefix_invalidateMainLoopTimerAsynchronous:(BOOL)returnsImmediately
{
void (^invalidator)() = ^{
[self invalidate];
};
dispatch_queue_t mainQueue = dispatch_get_main_queue();
if (returnsImmediately) {
dispatch_async(mainQueue, invalidator);
return;
}
if (![NSThread isMainThread]) {
dispatch_sync(mainQueue, invalidator);
return;
}
invalidator();
}
Note the thread checks before using dispatch_sync
because...
dispatch_sync
Discussion
[…] Calling this function and targeting the current queue results in deadlock.
(from The GCD Reference — emphasis mine)