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.