views:

612

answers:

2

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:

  1. Am I on the right path of suspecting the problem has to do with the NSRunLoop ?
  2. Is there a way I can start a NSTimer from a background thread and attach it to the main thread's NSRunLoop ?
+1  A: 

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.

Louis Gerbarg
A: 

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.

AlBlue