BuySellAds.com

Our DNA is written in Objective-C
Jump

NSNotifications and Background Threads

I’m coming out of highly concentrated work (approx 87 hours) on my latest project. It’s an app that allows you to organize a betting pool for the upcoming FIFA World Cup.

The simplest method of communicating with our web-based API we found to be having the server send array or dictionary PLISTs which I could load and parse in a single line of code synchronously. So first I created all the API calls in synchronous blocking mode. When they where working I added a method by the same name prefixed with “async” and would have the blocking code executed on an NSOperationQueue.

If the synchronous method needed zero or one parameter then you can use NSInvocationOperation of calling it and have the queue work it off in the background. In some cases more than one parameter has to be passed. Here an NSInvocation has to constructed with multiple parameters, which I explained previously.

Once the API call is done processing it needs to tell the app about its result. This is done conveniently by sending NSNotifications. And in all the places in the UI where specific notifications should have an effect, you simply subscribe to the notifications by adding an observer for them to the default NSNotificationCenter. NSOperationQueue automatically uses multiple threads and takes care of the autorelease pool. So any operation might either run on the main thread or on a background thread.

Warning: Crash Ahead!

This causes a problem I have only ever seen happen on Simulator, so I’m not sure if it would also happen on the device. Generally you want the NSNotifications to be sent on the main thread as well, especially if they trigger UI activities like dismissing a modal login dialog. I don’t know if Apple will tweak NSNotificationCenter to send on the main thread in the future, but until they do, here’s my drop in solution.

The following code combo would cause a nasty crash if run on a background thread.

// in the background method to create a user
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:status] forKey:@"Status"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DTBettingPoolUserCreated"object:niluserInfo:userInfo];

Subscribing to this notification would be a method to dismiss the modal login dialog, which most of the time it did appropriately.

[self dismissModalViewControllerAnimated:YES];

Sometimes, not always this would result in the following output:

2010-05-21 13:03:58.409 wcbettingpools[52251:6223] void WebThreadLockFromAnyThread(), 0x4857290:
    Obtaining the web lock from a thread other than the main thread or the web thread. UIKit
    should not be called from a secondary thread.
2010-05-21 13:03:58.409 wcbettingpools[52251:6223] bool _WebTryThreadLock(bool), 0x4857290: Tried
    to obtain the web lock from a thread other than the main thread or the web thread. This may be
    a result of calling to UIKit from a secondary thread. Crashing now...

Well, thankfully the crashing statement contains a hint to the error of our ways. “Calling to UIKit from a secondary thread” is precisely what this is doing. The NSOperation is on such a secondary thread, and the method we’re calling belongs to UIKit. Peeking at the top of our debugger also reveals that in fact we are not on the main thread.

So let’s not do that any more. The quick fix is to have default center perform the action postNotification on the main thread like this:

// post notification on main thread
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumbernumberWithInt:status] forKey:@"Status"];
NSNotification *note = [NSNotificationnotificationWithName:@"DTBettingPoolUserCreated"  object:html userInfo:userInfo];
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:note waitUntilDone:YES];

It works, but it’s ugly, and the compiler does not check for syntax errors in selectors. So we do the natural thing for a Cocoa Coder, we create a category extension to NSNotificationCenter.

NSNotificationCenter+MainThread.h

@interface NSNotificationCenter (MainThread)
 
- (void)postNotificationOnMainThread:(NSNotification *)notification;
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject;
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
 
@end

NSNotificationCenter+MainThread.m

#import "NSNotificationCenter+MainThread.h"
 
@implementation NSNotificationCenter (MainThread)
 
- (void)postNotificationOnMainThread:(NSNotification *)notification
{
	[self performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];
}
 
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject
{
	NSNotification *notification = [NSNotification notificationWithName:aName object:anObject];
	[self postNotificationOnMainThread:notification];
}
 
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
{
	NSNotification *notification = [NSNotification notificationWithName:aName object:anObject userInfo:aUserInfo];
	[self postNotificationOnMainThread:notification];
}
 
@end

With this you just replace every postNotificationName: with postNotificationOnMainThreadName: and don’t need to change anything else to safely send notifications on the main thread. That’s the kind of class you will want to have in your tool chest.

All the while I was assuming that NSNotificationCenter would be smart enough and “just work”, but I learned my lesson. These kinds of crashing bugs are sometimes hard to find, but teach you a great deal.


Categories: Recipes

%d bloggers like this: