Before keeping on reading in the docs, my brain got stuck at this point:
- (void)threadMainRoutine {
BOOL moreWorkToDo = YES;
BOOL exitNow = NO;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // is this THE run loop?
// some stuff...
while (moreWorkToDo && !exitNow) { // or is THIS the run loop?
// some stuff
[runLoop runUntilDate:[NSDate date]];
// some stuff
}
// some stuff
}
I've added some comments in the code example. Maybe someone can explain this, why there's a while loop if there's a runLoop object that receives a -runUntilDate: message. I mean: Who is the loop here? I see two. First the while that's obviously a running loop, and then it calls a method that sounds like running a loop a well.
stateConfused = YES;
pleaseExplain = YES;
Technically, the NSRunLoop loops internally (it loops "until date"). This gives you a periodic opportunity to quit the thread -- if you use run instead of runUntilDate: then the NSRunLoop will loop internally forever (you won't need to wrap it in a while loop but you can never stop it either). This is the normal behavior for the main thread, not worker threads which typically have exit conditions that need periodic checking.
The runLoop will never change the value of moreWorkToDo or exitNow (you are responsible for doing this when the thread's work is done or the user has quit your application) but these are how you decide if you want the thread to terminate.
Depending on how you want your thread to behave, you can replace these flags with [NSApp isRunning] and [tasksRemainingForThisThreadToProcess count] != 0 or similar.
(Race condition warning: If you end the thread when all remaining tasks are processed, be careful to never send another task to the thread when the tasksRemainingForThisThreadToProcess is empty since this is an indication that the thread will quit).
One is an object responsible for maintaining the run loop's state, the other is an actual loop.
the first one is the actual runloop, the runloop returned by [NSRunLoop currentRunLoop] is created by the UIApplication. Simplified, it receives all the OS events (Keyboard, Mouse etc.) and dispatches them to your program. If you call [runLoop runUntilDate:[NSDate date]]; this will return after the specified date has passed but your program will continue to receive OS events. See this link.
This is different from calling:
[[NSThread currentThread] sleepFormTimeInterval:]
,which would send your thread to sleep completely and it won't even receive OS events.
Related
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 sanity of future generations of cocoa programmers, I'm asking this question:
Is there an absolutely 100% sure way to have a scheduled, long-term timer fire at a later point in time, regardless of whether it was called from a background thread, main thread, etc?
It seems that I keep having to solve the same problem over and over again for the majority of my classes that use NSTimers. they work during short-term testing, let's say I set the timer to fire through a background thread to fire in 10 seconds. It works, because there's still a run loop running. But once I change the fire time to what I really want, like 15-30 minutes, there's dead silence. The run loop is gone and I don't know how to handle such a case. Nothing happens, and I discover such bugs a few days later, once I've already forgotten which timer would be responsible for that.
Currently I'm doing some really, really ugly dance with selectors, for example here's a test method(It seems to work for 10 minute timers):
//this is a test method to simulate a background task requesting a timer
[self performSelectorInBackground:#selector(backgroundReminderLongTermTest:) withObject:nil];
//this is a method similar to the one that the background thread would be trying to invoke
-(void)backgroundReminderLongTermTest:(id)sender
{
[self performSelectorOnMainThread:#selector(backgroundReminderFromMainThread:) withObject:nil waitUntilDone:NO];
}
//this is a wrapper for the background method, I want the timer to be added to a thread with a run loop already established and running
-(void)backgroundReminderFromMainThread:(id)sender
{
[playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:1800 target:self selector:#selector(start:) userInfo:nil repeats:NO]];
}
I like the convenience of not having to worry about creating a fire date object with the scheduled timers, but should I just forget about them and use timers with specific fire dates? It seems that the scheduledTimer works well for short term tasks, when the run loop is already present, but I simply cannot see this kind of bugs during the app's execution. At one point, it seems that the timers are firing normally, but at a later point they stop firing completely.
Thank you for any help or clarification. I'm looking for a method that schedules timers without having to worry about whether or not a run loop is present every time I need to schedule a timer. I want to be sure that as long as the app is running, my timers, scheduled through this method would fire at predictable points in the future .
One of the myriad issues with NSTimers is their run-loop dependency. Every thread has a single run loop. If you schedule a timer on a background thread, it will be scheduled on that thread's run loop. If that thread is short lived, which background threads often are, that timer will quietly die with it.
The solution is to guarantee the timer is run on a thread that will be alive when the timer fires. The best way to do these dedicated background timers in my experience is to not use NSTimer at all, and go for GCD timers instead. Better men than I have coded up GCD powered timers. I personally prefer Mike Ash's article and implementation, which comes with an explanation.
Use local notification instead.
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)
I've used both GCD and performSelectorOnMainThread:waitUntilDone in my apps, and tend to think of them as interchangeable--that is, performSelectorOnMainThread:waitUntilDone is an Obj-C wrapper to the GCD C syntax. I've been thinking of these two commands as equivalent:
dispatch_sync(dispatch_get_main_queue(), ^{ [self doit:YES]; });
[self performSelectorOnMainThread:#selector(doit:) withObject:YES waitUntilDone:YES];
Am I incorrect? That is, is there a difference of the performSelector* commands versus the GCD ones? I've read a lot of documentation on them, but have yet to see a definitive answer.
As Jacob points out, while they may appear the same, they are different things. In fact, there's a significant difference in the way that they handle sending actions to the main thread if you're already running on the main thread.
I ran into this recently, where I had a common method that sometimes was run from something on the main thread, sometimes not. In order to protect certain UI updates, I had been using -performSelectorOnMainThread: for them with no problems.
When I switched over to using dispatch_sync on the main queue, the application would deadlock whenever this method was run on the main queue. Reading the documentation on dispatch_sync, we see:
Calling this function and targeting
the current queue results in deadlock.
where for -performSelectorOnMainThread: we see
wait
A Boolean that specifies whether the
current thread blocks until after the
specified selector is performed on the
receiver on the main thread. Specify
YES to block this thread; otherwise,
specify NO to have this method return
immediately.
If the current thread is also the main
thread, and you specify YES for this
parameter, the message is delivered
and processed immediately.
I still prefer the elegance of GCD, the better compile-time checking it provides, and its greater flexibility regarding arguments, etc., so I made this little helper function to prevent deadlocks:
void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
if ([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
Update: In response to Dave Dribin pointing out the caveats section ondispatch_get_current_queue(), I've changed to using [NSThread isMainThread] in the above code.
I then use
runOnMainQueueWithoutDeadlocking(^{
//Do stuff
});
to perform the actions I need to secure on the main thread, without worrying about what thread the original method was executed on.
performSelectorOnMainThread: does not use GCD to send messages to objects on the main thread.
Here's how the documentation says the method is implemented:
- (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait {
[[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes];
}
And on performSelector:target:withObject:order:modes:, the documentation states:
This method sets up a timer to perform the aSelector message on the current thread’s run loop at the start of the next run loop iteration. The timer is configured to run in the modes specified by the modes parameter. When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in one of the specified modes; otherwise, the timer waits until the run loop is in one of those modes.
GCD's way is suppose to be more efficient and easier to handle and is only available in iOS4 onwards whereas performSelector is supported in the older and newer iOS.
While I usually make use of NSOperation for having a thread do a short task/operation, I'd like to have a long-living dedicated thread that is always available to process certain operations. For this, I allocate a new NSThread and use the initWithTarget method:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
My understanding is the selector passed in should be the thread's main method that is responsible for starting the runloop. What is the proper code to have in there?
Is a while loop like this appropriate...
(void)newThreadMainMethod {
while(1) {
[[NSThread currentThread] run];
}
}
or is there a more efficient way to do it so that the thread doesn't take up resources with a endless loop? I was thinking along the lines of having a timer wake up every 0.5 seconds and call run on the thread in case something new is available to work on. I would appreciate your input.
Thanks.
[[NSRunLoop currentRunLoop] run] is what you want, but it will carry on forever so you only need to call it once (not in a loop). You can either:
do that, terminating the thread from within the run loop when/if appropriate
use [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: JIFFY_TIME], and poll for an exit condition in a while() loop like you've written.
I have a "sync" task that relies on several "sub-tasks", which include asynchronous network operations, but which all require access to a single NSManagedObjectContext. Due to the threading requirements of NSManagedObjectContexts, I need every one of these sub-tasks to execute on the same thread. Due to the amount of processing being done in some of these tasks, I need them to be on a background thread.
At the moment, I'm launching a new thread by doing this in my singleton SyncEngine object's -init method:
[self performSelectorInBackground:#selector(initializeSyncThread) withObject:nil];
The -initializeSyncThread method looks like this:
- (void)initializeSyncThread
{
self.syncThread = [NSThread currentThread];
self.managedObjectContext = [(MyAppDelegate *)[UIApplication sharedApplication].delegate createManagedObjectContext];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
}
Is this the correct way to start up the NSRunLoop for this thread? Is there a better way to do it? The run loop only needs to handle 'performSelector' sources, and it (and its thread) should be around for the lifetime of the process.
When it comes to setting up an NSAutoreleasePool, should I do this by using Run Loop Observers to create the autorelease pool and drain it after every run-through?
I'm not 100% sure this is the most efficient way to solve your problem, but I'm doing like this...
Since the asynchronous network operations can take different time to complete (or to timeout) I keep track of them with an instance variable (in this case a BOOL called callComplete)
When the code for firing of the network reqeust is started, and my NSURLConnection has gone away and doing it's magic, I wait for the call to be complete like this.
while(!callComplete && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]){
// Here you can perform other checks, like making sure the method isn't running forever (with a timeout variable)
// Just set callComplete to YES when done
}
Regarding the NSAutoreleasePool, I simply create one on the start of my selector that is run in the background. Then after the Run Loop is done (and my calls are complete) i release as usual before returning.
I was just recently futzing about with a sample application trying to get my head completely wrapped around NSRunLoop. The sample I wrote created a simple secondary thread via NSOperation. The secondary thread does a few tasks such as process an NSTimer and also some rudimentary streaming with NSStream. Both of these input sources require a properly configured NSRunLoop in order to execute.
My question is this. Originally I had some code that looked like this in the secondary thread:
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:connectTimeout
target:self
selector:#selector(connectionConnectedCheck:)
userInfo:nil
repeats:NO];
[myRunLoop addTimer:self.connectTimer forMode:NSDefaultRunLoopMode]; // added source here
[myRunLoop run];
[NSStream getStreamsToHostNamed:relayHost port:relayPort inputStream:&inputStream outputStream:&outputStream];
if ((inputStream != nil) && (outputStream != nil))
{
sendState = kSKPSMTPConnecting;
isSecure = NO;
[inputStream retain];
[outputStream retain];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[inputStream open];
[outputStream open];
self.inputString = [NSMutableString string];
return YES;
}
Now, with the code above, the events would never process. Not on currentRunLoop. I did something terrible afterwards, as this was just an educational exercise, and modified it to run under NSRunLoop mainRunLoop. Worked like magic. However, I'm almost certain that depending on my main threads run loop in a secondary thread is on 10 different levels of wrong.
So my question is in two parts, and I hope that's OK.
What can possibly go wrong with the little 'hack' I applied in order to get the secondary thread to run and respond to events via the run loop?
What's the proper way to configure the secondary thread to listen to all events/timer based sources so I don't have to do step 1.
Thanks for the insight all.
Answering your questions in reverse order:
2. You've got two problems. The documentation for -[NSRunLoop run] says:
If no input sources or timers are
attached to the run loop, this method
exits immediately; otherwise, it runs
the receiver in the
NSDefaultRunLoopMode by repeatedly
invoking runMode:beforeDate:. In other
words, this method effectively begins
an infinite loop that processes data
from the run loop’s input sources and
timers.
So using your thread's own run loop, there are probably not input sources defined for the run loop, so it is returning immediately. If there are, your run loop is looping infinitely, and the rest of your code after that point is never being executed.
In order to get things to work correctly, your run loop needs to first have some input sources, and then needs to run periodically to check for events. Note that you don't want to use [NSRunLoop run], as you'll never get control back. Instead, I would recommend setting up a loop right before return YES that continually runs the run loop until the thread has been cancelled, or until you're done streaming data. That will allow the run loop to process data as it arrives. Something like this:
while (!done) {
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
[NSRunLoop runUntilDate:] processes events until the specified date, and then returns control to your program so that you can do whatever you want.
1. It's working because your main thread's run loop is being run periodically, so events are being handled. If your main thread ever blocked, however, your data would stop arriving. This could be particularly bad if the main thread was waiting for data from your second thread. Also, NSRunLoop is not thread-safe:
Warning: The NSRunLoop class is
generally not considered to be
thread-safe and its methods should
only be called within the context of
the current thread. You should never
try to call the methods of an
NSRunLoop object running in a
different thread, as doing so might
cause unexpected results.
(from the NSRunLoop documentation.)
Apple's Threading Programming Guide has a section entitled Run Loop Management, which explains all of this to some degree. It's not the clearest document I've ever read, but if you're working with run loops, it's a good place to start.
Did you remember to run the runloop?
[[NSRunLoop currentLoop] run]
First off, the scheduledTimer... method is supposed to add the timer to the current run loop automatically. You only need to use the addTimer: method if you created the timer using the initWithFireDate... initializer. I doubt adding the timer twice would cause a problem, but it's a possibility.
The run method of NSRunLoop is not supposed to return until there are no more event sources or the loop is explicitly exited. That means you need to schedule any event sources before calling run. Move your [myRunLoop run] call to the very end of your code sample. (Also, obviously, eliminate the return statement)
The bottom line is that if [NSRunLoop run] is returning, then you don't have an event source scheduled on it. Use the debugger to step through the code. If you see it progress past the [NSRunLoop run] call, you can be sure you have a problem with your input sources.