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!

blog comments powered by Disqus

About this Entry

This page contains a single entry by Dave published on May 22, 2008 1:14 AM.

Mac Developer Roundtable: Version Control was the previous entry in this blog.

Trashing (from) the Command Line is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Links

Powered by Movable Type 4.1