iOS Programming for Multicore Processors

With the impending release of the iPad 2, understanding how to program multithreaded applications will quickly become paramount as applications continue to push the envelope to make immersive experiences with high-performance computation. Now, without actually having a multicore iPad in my hands, I can’t say exactly how the system will behave, but there are a few best practices we should all be aware of when writing iOS code:

  • Using Core Data? You can’t share access to an NSManagedObjectContext across multiple threads, dispatch queues, or NSOperation queues, so for each one you’ll need to create a new instance. Similarly, don’t pass an NSManagedObject or subclass thereof between threads. Give each of your objects a unique ID—CFUUID works well for this—and pass the ID around, pulling a new object out of your NSManagedObjectContext for each thread. It’s a pain, but that’s how to (safely) get around threading and Core Data.
  • Always call UIKit updates from the main thread. Whatever you’re doing to your user interface, be it updating a label, loading an image into an image view, anything that’ll be rendered to screen—and some things that won’t—should be run on the main thread. There are two main ways to do this:
    1. Use Grand Central Dispatch. Using dispatch_get_main_queue(), you can get a reference to the main queue and submit blocks to it for updating your UI. This is typically a clean, easy way to refactor existing code for thread-aware programming.
    2. Use -performSelectorOnMainThread:withObject:waitUntilDone: and friends. This has the drawback of only working for methods that take one or zero Objective-C objects as arguments, but can be a quick and easy way to use fire-and-forget methods like -reloadData on UITableView.
  • Think about how you declare your properties. How many people have been using atomic properties? Before now, not many. In fact, before now, it was typically useless, as the chances of something interrupting your accessor methods was pretty low. Now, though, if you’re planning on accessing an object from multiple threads, you really need to control access to your properties.
  • Use locks. Locks, long the scourge of the multithreaded-code author, are simply essential for some parts of multithreaded programming. Whether you’re using NSLock or a lower-level lock, or even something like Grand Central Dispatch’s counting semaphore type, dispatch_semaphore_t, protect critical regions of your code from multiple accessors with (carefully-thought-out) locked access.

This list is by no means exhaustive, and Apple can do a lot to make this irrelevant (such as make UIKit threadsafe, which would be a killer iOS 5.0 feature), but with the iPad 2’s arrival, developers can no longer assume safety from threading problems. Be sure also to read Apple’s Threading Programming Guide to get anything I’ve left out.

It also makes a great excuse to buy an iPad 2. I mean, you need to test this, right?

Cocoa Touch Tutorial: Using Grand Central Dispatch for Asynchronous Table View Cells

One of the problems that an iOS developer will often face is the performance of table view cells. Table view cells are loaded on-demand by the UITableView that they’re a part of; the system calls ‑cellForRowAtIndexPath: on the table view’s dataSource property to fetch a new cell in order to display it. Since this method is called (several times) while scrolling a table view, it needs to be very performant. You don’t have very much time to provide the system with a table view cell; take too long, and the application will appear to stutter to your users. This kills the immersion of your application and is an instant sign to users that the application is poorly-written. I guess what I’m saying is that this code needs to be fast. But what if something you need to do to display the table view cell takes a long time—say, loading an image?

In my MobiDevDay presentation a couple of weeks ago, I illustrated a solution to this problem: Grand Central Dispatch. GCD, Apple’s new multiprocessing API in Mac OS X Snow Leopard and iOS 4, is the perfect solution for this problem. Let’s take a look at how it works.

Grand Central Dispatch operates using queues. Queues are a C typedef: dispatch_queue_t. To get a new global queue, we call dispatch_get_global_queue(), which takes two arguments: a long for priority and an unsigned long for options, which is unused, so we’ll pass 0ul. Here’s how we get a high-priority queue:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

It’s pretty straightforward. To use this queue, we add blocks of code onto it. Typically this is done with blocks (Apple’s new code encapsulation extension to the C language), though it can be done with C functions. To submit a block onto a queue for execution, use the functions dispatch_sync and dispatch_async. They both take a queue and a block as parameters. dispatch_async returns immediately, running the block asynchronously, while dispatch_sync blocks execution until the provided block returns (though you cannot use its return value). Here’s how we schedule some code onto a queue (we’ll assume this code runs after our previous example, so queue is already defined):

dispatch_async(queue, ^{
    NSLog(@"Hello, World!");
});

It’s very easy to forget the ); at the end of that line, so be careful.

How does this apply to table view cells? Let’s take a look at a typical scenario for loading images from disk:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ExampleCell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Get the filename to load.
    NSString *imageFilename = [imageArray objectAtIndex:[indexPath row]];
    NSString *imagePath = [imageFolder stringByAppendingPathComponent:imageFilename];
    
    [[cell textLabel] setText:imageFilename];
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    [[cell imageView] setImage:image];

    return cell;
}

The problem with that code is that creating image blocks until ‑imageWithContentsOfFile: returns. If the images are especially large, this is catastrophic. Modifying this code to use Grand Central Dispatch is simple:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Get the filename to load.
    NSString *imageFilename = [imageArray objectAtIndex:[indexPath row]];
    NSString *imagePath = [imageFolder stringByAppendingPathComponent:imageFilename];
    
    [[cell textLabel] setText:imageFilename];
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

    dispatch_async(queue, ^{
        UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    
        dispatch_sync(dispatch_get_main_queue(), ^{
            [[cell imageView] setImage:image];
            [cell setNeedsLayout];
        });
    });
    
    return cell;
}

First, we create our image asynchronously by using dispatch_async(). Once we have it, however, we have to come back to the main thread in order to update our table view cell’s UI (all UI updates should be on the main thread, unless you like reading crash reports). GCD has a function to get the main queue—analogous to the main thread—called dispatch_get_main_queue(). We can dispatch a block to that thread to update the UI.

By making this simple modification, we can very easily improve the performance of our table view. There are a few steps remaining, however, and this method has one serious shortcoming: if the cell is re-used by the time the image loads, it can load the wrong image into the cell. To get around this, it would be better to cache the images in an array or a dictionary (just be sure to release it in your view controller’s ‑didReceiveMemoryWarning: method). That said, this is an example of something you can do quite easily to improve the performance of your application. The better it performs, the more your users will like it, and that’s the ultimate goal.

The code used in this post is available as a GitHub repository.

MobiDevDay Presentation Slides

I gave a presentation on blocks and Grand Central Dispatch today at MobiDevDay. You can download the slides at SlideShare.

Looking for more reading about blocks? Here are some more resources:

Also, as we saw in the presentation, check out cdecl.org to cheat!

UPDATE: Looking for example code? I’ve put some of the code that went into the slides up on GitHub. It’s light, but it also includes a .PDF version of the presentation.

iPhone Programming: Hard Mode

After a conversation with some co-workers, I discussed how it would be technically possible to write an iPhone app using only a main.m file—no separate class files. This post is the result of that. It’s definitely doable, but not something I’d ever recommend for shipping code.
The code is explained below, but you can get the full source in my public GitHub repository.

Getting Started

By default, an iPhone application template gives you a main.m file, but it’s pretty basic:

#import <UIKit/UIKit.h>

int main(int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

To go from that code to running your application, a few things happen, mostly in UIApplicationMain. Its last argument, typically nil, is a pointer to an NSString that specifies the class name of the application delegate. Normally, your Info.plist file specifies a nib file (the keys NSMainNibFile or NSMainNibFile~ipad), which in turn specifies the application delegate, instantiates it, etc.

So, first, delete the MainWindow.xib file, then all other class files (MyCoolAppDelegate.h and MyCoolAppDelegate.m, for instance). There should be three files left in the iPhone application: your Prefix.pch file (which we could technically do without), main.m, and your Info.plist file.

The Fun Begins

Administrivia: Creating Strings

Since, as a part of the exercise, we don’t want to use Objective-C, I also decided against using any constant NSStrings (@"Hello, World!" and the like). To create a string, then, we’ll use CFStrings. I created a macro to create one from a C string using ASCII encoding:

#define JK_EasyCFString(X)
(CFStringCreateWithCString(kCFAllocatorDefault, (X), kCFStringEncodingASCII))

Don’t forget to call CFRelease() when you’re done with it, though.

Creating a Class

We need to modify the call to UIApplicationMain to include our application delegate class if we want this app to be anything more than a blank screen. What class name can we give it, though? We’ll need to create a class, and we’ll call it “HardModeAppDelegate.”

	// Create a class to serve as our application delegate.
	Class appDelegate = objc_allocateClassPair(NSClassFromString((id)NSObjectString), "HardModeAppDelegate", 0);

The first argument is a toll-free bridged CFString that specifies NSObject as the superclass, then we pass in the name of the class as a C string and 0 for the number of extra bytes we want (which is nearly always zero). Now let’s set it up a bit:

	// Conform to the UIApplciationDelegate protocol.
	Protocol *appDelegateProto = NSProtocolFromString((id)UIApplicationDelegateString);
	class_addProtocol(appDelegate, appDelegateProto);

	// Add methods.
	SEL applicationDidFinishLaunchingWithOptions = NSSelectorFromString((id)appDidFinishLaunchingOptionsString);
	class_addMethod(appDelegate,
					applicationDidFinishLaunchingWithOptions,
					(IMP)UIApplicationDelegate_ApplicationDidFinishLaunchingWithOptions,
					"v@:@@");

	SEL dealloc = NSSelectorFromString((id)deallocString);
	class_addMethod(appDelegate,
					dealloc,
					(IMP)appDelegate_dealloc,
					"v@:");

	// Add an instance variable for the window.
	class_addIvar(appDelegate,
				  "window",
				  sizeof(id),
				  log2(sizeof(id)),
				  "@");

	// Now that we’ve added ivars, we can register the class.
	objc_registerClassPair(appDelegate);

That code is the same as writing an @interface block for a new class, setting up ivars, conforming to protocols, and defining instance methods, but done using the runtime calls instead. Methods are actually C functions, but with two arguments prepended to the argument list: id self and SEL _cmd. Those arguments are actually passed to every Objective-C method, but usually hidden.

Custom Message Sending

The rest of the application is more of this bootstrapped Objective-C in C, but there are a few wrinkles worth discussing, most notably the use of objc_msgSend with methods that return other than id and/or have additional arguments beyond self and _cmd. For instance, to call -bounds on a UIScreen object, I had to cast the return type of objc_msgSend to CGRect:

CGRect (*msgSendBounds)(id self, SEL _cmd);
  msgSendBounds = (CGRect(*)(id, SEL))objc_msgSend_stret;

Similarly, when calling -initWithFrame: on a UIWindow or UILabel, I casted it to take in a CGRect argument:

id (*msgSendCGRect)(id self, SEL _cmd, CGRect rect);
msgSendCGRect = (id(*)(id, SEL, CGRect))objc_msgSend;

Accessing Instance Variables

In my implementation of the HardModeAppDelegate class’s -dealloc method, I need to access the window instance variable to send it a -release message. Using the Ivar type and the object_getIvar function, it becomes easy:

Ivar windowIvar = class_getInstanceVariable(self->isa, "window");
id window = object_getIvar(self, windowIvar);

So what do we have?

To be clear, this is not writing an iPhone app without using Objective-C, per se. The runtime is still being used, messages are being sent, all that. But it is an illustration of some of the heavy lifting that the runtime does for you. I would caution against adopting this practice for real, shipping apps.