Invoke on Main Thread
Threaded programming on Mac OS X, as with almost any platform, is fraught with danger. One of the biggest snags is that AppKit, the GUI framework, is not thread safe. In order for things to work properly, you almost always need to update GUI classes from the main thread, only. Fortunately, Objective-C makes it relatively easy to run a bit of code on the main thread. If you’re on a background thread or in an NSOperation
, you can invoke one of the performSelectorOnMainThread:
variants of NSObject
. For example:
[person performSelectorOnMainThread:@selector(setName:)
withObject:newName
waitUntilDone:NO];
This calls the -setName:
method of the Person
class, passing in a new name. You also have the option of waiting for the method call to finish before proceeding in the background thread.
There are a few problems with performSelectorOnMainThread:
and friends:
- You can only pass a single argument.
- You cannot pass a primitive types.
- The syntax is quite verbose.
I’ve come up with a better way of doing this as a category to NSObject
called dd_invokeOnMainThread
. Here’s how the preceding call would look:
[[person dd_invokeOnMainThread] setName:name];
This solves all three problems of performSelectorOnMainThread:
and friends:
- You can pass as many arguments as you like.
- You can use primitive types.
- The syntax is less verbose.
Thus, even a method call like this is possible:
[[someObject dd_invokeOnMainThread] setInt:5 withObject:foo];
The magic is to use NSInvocation
. Of course NSInvocation
is itself usually quite verbose to use, but my friend Jonathan Wight of Toxic Software came up with a great way of simplifying NSInvocation
with a class called CInvocationGrabber
. I used Jonathan’s code as a starting point and created a variant called DDInvocationGrabber
which is itself used in the NSObject
category:
@interface NSObject (DDExtensions)
- (id)dd_invokeOnMainThread;
- (id)dd_invokeOnMainThreadAndWaitUntilDone:(BOOL)waitUntilDone;
@end
dd_invokeOnMainThread
uses waitUntilDone
as NO
since that’s the most common case, in my experience.
The code is part of my DDFoundation project, which is a set of extensions to the Foundation library. This is just the latest addition to DDFoundation. Grab the source tarball (version 1.0b1 as of this posting) if you want to use it or just see how it’s done. If your only interested in dd_invokeOnMainThread
, you’ll need DDInvocationGrabber
and NSObject+DDExtensions
. I’ve been using this quite successfully in a current project and it has made my life just that wee bit easier. Enjoy!