02 Jun 2009 Run-loops vs. Threads in Cocoa
As a relative newby to the world of Cocoa programming (on the iPhone in particular), I have spent some time trying to understand if and when you’d use a run-loop instead of launching a separate thread. I was unable to find any definitive answer on the web, so ended up joining the dots myself. What follows is my understanding of when you’d want to use one or the other. Cocoa experts are welcome to comment if I’ve got it wrong.
The Problem
Touches aren’t the only source of input to an iPhone application. For example, another source can be a socket – sometime you want to listen to a socket for data. But you don’t want the UI to lock up whilst it’s listening – you still want input from the user to be dealt with promptly. Similarly, you might want events to be triggered automatically at certain time intervals, but without locking up the application in the interim.
Coming from other UI frameworks, you might think that the way to deal with this is to to use a separate thread. That way, the thread can block on the socket or sleep for a particular time interval. However, as we all know, the introduction of multiple threads immediately introduces a bunch of potential defects that are difficult to reproduce and fix.
The Solution
Enter run loops. Or more specifically, the run loop – each iPhone application has one by default and for our purposes, this is all we need.
So what exactly is a run loop?
Well, first consider this assertion:the vast majority of the time that your Cocoa application is running, it’s doing nothing. More specifically, it’s waiting for input. However, as soon as you touch the screen, an event gets triggered, which may in turn result in some of your code being executed. If some data comes into a socket, or a timer fires, the same applies.
The key things is that once this code has been executed, the application goes back to waiting for input. Furthermore, in many cases the execution time of your code will be very small relative to the time the application spends waiting for input.
I think of run loops as a mechanism that exploits this fact.
A run loop is essentially an event-processing loop running on a single thread. You register potential input sources on it, pointing it to the code that it should execute whenever input is available on those sources.
Then when input comes into a particular source, the run loop will execute the appropriate code, then go back to waiting for input to come in again to any of it’s registered sources. If input comes into a registered source whilst the run-loop is executing another piece of code, it’ll finish executing the code before it handles the new input.
The upside of this is that whilst you mightn’t know exactly what order things are going to come in, at least you know that they’ll be processed one after the other instead of in parallel. This means that you avoid all of those nasty multi-threading issues that were described earlier. And that’s why run loops are useful.
Run loop scheduling in action
By default, all touch events received by an iPhone application are queued for processing by the application’s main run loop, so there’s nothing special you need to do for UI components. However, other sources of input require additional coding.
To schedule an NSInputStream on a run loop, you’d do something like this:
...
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
...
This code sets it up so that whenever input is available on ‘iStream’, a ‘stream:handleEvent’ message will be sent to ‘self’. Note that the stream could be from any sort of source, including a socket.
Another object that can be scheduled on a run loop is a timer. For example:
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(doStuff)
userInfo: nil
repeats:YES];
will schedule a timer on the current run loop to send a ‘doStuff’ message to ‘self’ every two seconds.
When not to use a run loop
So when wouldn’t you use a run loop? Well, if you had some event-handling code that was going to take a long time to execute (for example, performing some CPU-intensive calculation), then everything else in the event-handling queue won’t get handled until it’s finished. This would cause your application to become unresponsive until the processing has finished. In that sort of scenario, you might want to consider using a separate thread to do the processing.
However, for the vast majority of cases, our code for handling events – be they from the screen, sockets or timers – takes a very short time to execute. And that’s why it’s easier (and safer) to just use the main run loop to handle those events.
The trade-offs
The only downside to using a run loop instead of a thread is that instead of just whacking a thread around a whole section of code that you know will block in one or more places, you have to go to each potential blocking point, register the source on the run loop, and implement a callback to process events that are generated from that source.
Whilst this may seem like some effort, it pales in comparison to the pain that can result from poorly-considered threading. So next time you’re tempted to use a thread to read from a blocking input source, consider taking the time to use a run loop. It could well save you a lot of time in the long run.
Adi
Posted at 14:38h, 14 AprilHi, thanks for your bried explanation. I too couldn’t find a good explanation about the difference between thread (NSTask) and RunLoop from the web. It’s getting clearer right now, but I still have a question or two.
For example: I’m creating a simple program that is using a NSTimer as a counter. The timer fire the selector (that adds up the counter value) every certain interval defined in the method. Let’s say Wait(100), means wait for 100 mSec. then the Wait method will check the counter if it’s already 100 msec (in this case, interval is 0.01). I assume you have to use a task or thread in order to do this. Is this correect, or you could still use a runloop?
Rani
Posted at 16:40h, 17 NovemberYou can use runloop here as the callback which needs to be called upon firing the timer is doing very simple task.
Ted Neward
Posted at 19:50h, 23 MayBen,
I think you got this all wrong. I see run loops on the iPhone, or within the Cocoa context, as analogous to the “main game loop” in the context of game programming. Games loops have the following properties: they are tight, the are efficient, and they run well. To that end, game loops execute the usual process events (inputs, game events, etc.), update (physics engine update, sprite/game object updates), followed by the all important render. All of this is usually locked into a tight frame rate of say 60Hz, that is 1/60 frames per second.
Now that is tight, just like run loops.
Goman
Posted at 14:07h, 04 FebruaryHi Ben,
Thank you very much for your explanation.
I was spent time to find what’s is the run loop in the internet, but I can’t find any one which is very clear until I get your blog.
Do you mind if I translate this article to Chinese?
Goman
Ben Teese
Posted at 08:21h, 06 FebruaryHi Goman,
That’s fine, just be sure to link to the original post 🙂
Cheers,
Ben
Goman
Posted at 17:07h, 07 FebruaryThank you, please refer to:”http://user.qzone.qq.com/24431188″
Dhiraj
Posted at 12:59h, 24 AprilThanks for the nice tutorial, I had bit confusion between Threads and Run loop. Now is very much clear.
Can you post a complete code example for the same ?
–
Dhiraj
jack
Posted at 13:31h, 11 Septembergreat
jack
Posted at 13:32h, 11 Septemberi have a question.
when i use NSURLConnection asynchronously, is it using the main run loop?
Andrii
Posted at 03:23h, 26 JanuaryI hope not) otherwise connection wouldnt have been async in respect to the UI
eugenio
Posted at 02:04h, 04 JulyI know you’ve written this blog some time ago now but I still find it the best simple explanation of run loops I’ve found so far.
Well done and thanks!