Multithread Cocoa (Run Loops)

When writing multithreaded Cocoa apps (on Mac OS X or on iPhone OS) it’s necessary to consider the “run loop(s)” of your application. Run loops are the message pumps which drive (among other things) all the parts of your application that you don’t have to write (e.g. low-level rendering of the user interface, handling keyboard and mouse events, and so forth). In single-threaded applications you can largely ignore the run loop, since it’s set up automatically, but multi-threaded apps present more complicated questions.

Per-Thread

The important point about run loops is that they are per-thread processes; each loop is executed on a single thread, and each thread may or may not have its own run loop. Furthermore, each thread has its own event queue from which its run loop (if any) will draw messages, and to which Cocoa code executed on that thread will deliver messages.

Messaging

Note: This section is conjectural.

The Cocoa run-time lives and dies by message passing; when you update the contents of a widget (e.g. [label setText:@"Foo"], where label is a UILabel), Cocoa dispatches a message to the current thread’s event queue indicating that the widget should be redrawn. The display isn’t actually updated until the thread’s run loop picks up and processes this message. This arrangement is problematic if the current thread has no run loop; by default, secondary (user-created) threads are not assigned run loops.

Example

Let’s say you have a simple view controller, with only two members:

  • view, bound to the controller’s view
  • label, bound to a UILabel in that view

Here’s how you might initialize the label on load, in a single-threaded application (all the functions which follow are methods of the view controller discussed above):

- (void)viewDidLoad {
	[super viewDidLoad];

	[label setText:@"Foo"];
}

Now, here’s how you might initialize it in a multithreaded application:

- (void)viewDidLoad {
	[super viewDidLoad];

	[NSThread detachNewThreadSelector:@selector(helper) toTarget:self withObject:nil];
}

- (void)helper:(id)dummy {
	[label setText:@"Foo"];
}

Unfortunately, this won’t necessarily work. The setText call is made on the newly-spawned thread, so any resulting messages (e.g. the redraw-request message) will be sent to that thread’s event queue and will never be processed, since the new thread has no run loop. The widget will only be redrawn when some event on the main thread happens to require such a redraw.

Solutions

There are two possible approaches to this problem: First, the newly-created thread can be given a run loop. Second, the newly-created thread can request that certain calls be made on the main thread. The second approach is better suited to simple cases, and might look like this:

- (void)helper:(id)dummy {
	[label performSelectorOnMainThread:@selector(setText:) withObject:@"Foo" waitUntilDone:YES];
}

Read about performSelectorOnMainThread here.

Significance

The performSelectorOnMainThread method points to one of the differences between Objective-C “messages” (i.e. the [foo bar:quux] business, not the run loop/event queue stuff discussed above – although they are related, and that’s sort of the point) and the object method calls seen in other languages. It would be a pretty exotic thing to make a function call on another thread in C++, but that functionality is a part of the basic NSObject class here. Food for thought.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

Comments are closed.