Reputation: 25011
For a game I'm developing, I call an expensive method from one of the touch processing routines. In order to make it faster, I decided to use performSelectorInBackgroundThread
, so instead of:
[gameModel processPendingNotifications];
I switched to:
[gameModel performSelectorInBackground:@selector(processPendingNotifications) withObject:nil];
The first problem I had, is that processPendingNotifications
did not have a NSRunLoop
, so I added it, like this:
- (void)processPendingNotifications {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pendingNotificationsQueue makeObjectsPerformSelector:@selector(main)];
[pendingNotificationsQueue removeAllObjects];
[pool drain];
}
So far so good. My problem is that some of the methods that are called from this background thread create new NSTimer
instances. These instances, end up not firing. I think this is because the secondary thread I have doesn't have a (or is ending its) NSRunLoop
. I'm starting the new timers by using:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(timerFired) userInfo:nil repeats:NO];
My questions are:
NSRunLoop
?NSTimer
from a background thread and attach it to the main thread's NSRunLoop
?Upvotes: 2
Views: 4684
Reputation: 24060
The NSTimers actually just periodically fire events into the enclosing NSRunLoop, which each thread has (or should have). So, if you have a child (or background) process running in a different thread, the NSTimers will fire against that thread's NSRunLoop instead of the application's main NSRunLoop.
You could ensure that timers are always created against the main runloop by sending it the addTimer:forMode: message with your newly instantiated (but not started) NSTimer. Accessing the main application's run loop is done with [NSRunLoop mainRunLoop], so regardless of which thread you're in, doing [[NSRunLoop mainRunLoop] addTimer:[NSTimer timerWithTimeInterval:20.0 target:self selector:@selector(timerFired) userInfo:nil repeats:NO]] forMode:NSDefaultRunLoopMode] will always schedule the timer against the main runloop.
However, bear in mind that the execution is not guaranteed at that interval, and if your main loop is busy doing something your timer will be left waiting until it's ready.
It's worth considering NSOperation and NSOperationQueue if you really want background activity to occur.
Upvotes: 3
Reputation: 43462
Yes, you need a runloop to dispatch the timer events
NSTimers are implicitly attached to the runloop of the thread they are created on, so if you want it to be attached to the main thread's runloop create it on the main thread using performSelectorOnMainThread:withObject:waitUntilDone:. Of course that code will execute on the main thread.
If your question is "Can I have a timer run on the main thread's run loop that directly runs a selector on another thread?" The answer is no, but the selector it fires on the main thread could just performSelector:onThread:withObject:waitUntilDone:. Of course that requires the thread you are trying to perform the selector against have an operational runloop.
If you want the code the timer is triggerring running on a background thread then you really need to get the background thread's runloop going, and if you do that then you don't need to schedule anything with the main thread since you will have an operational runloop.
Upvotes: 8