Chris Umbel

Asynchronous Programming in Cocoa Touch

It's unavoidable. Pretty much anywhere you go as a programmer these days concurrency is important. It's no less important on mobile platforms like the iPhone. In this post I intend to outline the fundamental techniques necessary for asynchronous programming on the iPhone. While these techniques could very well apply to standard, desktop Cocoa I'll focus on Cocoa touch.

I'll cover three basic approaches: NSObject's performSelectorInBackground message, NSThread and NSTimer.

NSObject's performSelectorInBackground message

NSObject's performSelectorInBackground message can be used to easily farm off tasks that run asynchronously. Here's an example that could be placed in a view controller.

-(void) workerThread {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

	NSString *str = [NSString stringWithContentsOfURL:[NSURL URLWithString:@"http://www.chrisumbel.com"]];

	[pool release];  
}

- (void)viewDidLoad {
    [super viewDidLoad];

	[self performSelectorInBackground:@selector(workerThread) withObject:nil];
}

performSelectorInBackground simply executed workerThread asynchronously. Not much to it.

Essentially that just loaded a string with the contents of a web page asynchronously in about the most raw form possible in Cocoa. Obviously this is far more simplistic than anything you'd do for production but it is a potentially long-running, I/O-bound operation which makes it a fine candidate for asynchronous execution. Note that it was unconcerned with synchronization and did not communicate back with the main thread.

Check out the the use of NSAutoreleasePool. It's imperative that you create an NSAutoreleasePool initially and release it before you exit the thread. It's responsible for memory management in the thread body.

NSThread

The most obvious class involved in asynchronous operations is NSThread which, as you would guess, effectively owns a separate thread of execution. Consider the following functional equivalent of the example above:

-(void)workerThread {
    // setup the thread's memory management.
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

    // grab the contents of a web page
    NSString *str = [NSString stringWithContentsOfURL:[NSURL
        URLWithString:@"http://www.chrisumbel.com"]];
	
    [pool release];  
}

-(void)viewDidLoad {
    [super viewDidLoad];

    // spawn a worker thread
    [NSThread detachNewThreadSelector:@selector(workerThread) 
	toTarget:self withObject:nil];
}

Thread Communication

Now I'll alter the example slightly to communicate back to the main thread. This is useful because you nearly always want to execute UI code on the main thread. The following example simply outputs its results to a label.

-(void)updateContent:(NSString *)content {
    [outputLabel setText:content];
}

-(void)workerThread {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

    NSString *str = [NSString stringWithContentsOfURL:
        [NSURL URLWithString:@"http://www.chrisumbel.com"]];
    
    // send our results back to the main thread
    [self performSelectorOnMainThread:@selector(updateContent:)
        withObject:str waitUntilDone:NO];
    
    [pool release];  
}

-(void)viewDidLoad {
    [super viewDidLoad];

    [NSThread detachNewThreadSelector:@selector(workerThread) 
	toTarget:self withObject:nil];
}

It's NSObject's performSelectorOnMainThread message that facilitates the inter-thread communication. Arguments to the target message (updateContent in my case) are passed along via withObject.

Synchronization

Mutex behavior is accomplished via the NSLock class. The following example synchronizes appending the results of the URL-lookup to a local file.

NSLock *myLock = nil;

-(void)workerThread:(NSString *)urlString {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

    NSString *str = [NSString stringWithContentsOfURL:
	[NSURL URLWithString:urlString]];

    // block other threads	    
    [myLock lock];
	
    FILE *f = fopen("content.txt", "a+");
    fputs([content UTF8String], f);
    fclose(f);
	
    // stop blocking
    [myLock unlock];

    [pool release];  
}

-(void)viewDidLoad {
    [super viewDidLoad];

    // create the lock object
    myLock = [[NSLock alloc] init];
	
    // spawn worker thread 1
    [NSThread detachNewThreadSelector:@selector(workerThread:) 
	toTarget:self withObject:@"http://www.apple.com"];
    
    // spawn worker thread 2
    [NSThread detachNewThreadSelector:@selector(workerThread:) 
	toTarget:self withObject:@"http://www.gnome.org"];
}

NSTimer

The NSThread examples above were true, honest-to-goodness multi-threaded where each task was running in a separate NSRunLoop (Cocoa event handling loop). By contrast the following code uses an NSTimer running on the main run-loop to periodically execute a timed operation. NSTimers aren't perfectly accurate, however. You shouldn't rely on them being real-time.

NSTimer *timer = nil;
NSInteger timesExecuted = 0;

-(void)timerTick {
    timesExecuted++;
    [outputLabel setText:[timesExecuted stringValue]];
}

-(void)viewDidLoad {
    [super viewDidLoad];

    // create a timer that ticks every 10 seconds and executes timerTick
    // which I defined above
    timer = [NSTimer scheduledTimerWithTimeInterval: 10.0 target:self 
	selector:@selector(timerTick) userInfo:nil repeats: YES];
}

Conclusion

Well, there's the basics. For more depth you can check out Apple's iPhone Threading Programming Guide It's quite comprehensive and definitely worth the read.

Sun Jan 17 2010 00:01:00 GMT+0000 (UTC)

Follow Chris
RSS Feed
Twitter
Facebook
CodePlex
github
LinkedIn
Google