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.
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.
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 scheduleInRunLoop:[NSRunLoop currentRunLoop]
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:
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 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.