Saturday, May 29, 2010

Objective-C Multi-Threaded Gotcha

Last week I ran into a curious crashing bug in Objective-C. The main class I was working with, call it Foo, simply needed to perform a long running operation in a background thread. Should be pretty simple right? The basic outline of the code looked like this:


@implementation Foo

- (id) init {
self = [super init];
if (self) {
// allocate members
}
return self;
}

- (void) dealloc {
// release members
[super dealloc];
}

- (void) createAndStartBackgroundThreadTodoStuff {
[self performSelectorInBackground:@selector(doStuff)
withObject:nil];
}

- (void) doStuff {
// do stuff
}

@end

The intention here is that all of the above methods should run on the main thread except the one responsible for performing the background task [Foo doStuff]. In this way I should only need to worry about thread safety issues related to [Foo doStuff].

As it turns out two of the above methods can get called in a background thread: [Foo doStuff] and [Foo dealloc].

[Foo dealloc] can be called because the performSelector* family of methods retain the receiver (self) and release it when the method invocation completes. If the autorelease pool on the background thread ends up with the last reference to the object then dealloc gets called there.

So why the crash? Well one of my member variables was not thread safe (UIWebView) and crashed when release was called from a background thread.

Memory management and multi-threading are probably the hardest aspects of programming to get right. It is even harder to get right when the issues overlap.

3 comments:

Dennis said...

140 characters on Twitter is not enough to respond :)

In regards to Cocoa, typically if you are using threads, you are doing something wrong -- not always, of course, but Cocoa provides the runloop so that you can handle network connections, respond to external events and haver timers without having to resort to threads. As you've found, problems like this come up more often when you choose to go down the road of using threads.

The only good reason to use threads under Cocoa is when you have a CPU intensive operation that needs to be done. In that case, however, you shouldn't necessarily hold on to UIKit objects. If you need to send a notification back to the main thread, you should have your application delegate or one of your view controllers be the target. And then use performSelectorOnMainThread to actually do the callback.

If you can post up some more of what you were trying to do, I might be able to point you in a different direction :)

mpv said...

Dennis, I totally agree with your comments. Multi-threading *should* be used sparingly and Objective-C does better than most languages in helping you do so. I could have just as easily written a post blasting the design of the code I was debugging. But I found the relationship between threads and memory management more interesting though.

Webster said...

I don't have anything constructive to add here, I need to fire up X-Code and play with iPhoney goodness.

I got a decent idea for an app but no clue if its even plausible.