Home > Blogs > Ask Big Nerd Ranch: Blocks in Objective-C

Ask Big Nerd Ranch: Blocks in Objective-C

Adam Preble answers a question about blocks.

Q. There's a lot of hype around blocks, but my apps aren't complicated enough to need operation queues or use Grand Central Dispatch. Are blocks useful in simpler apps like mine, and can you give me any examples?

A. Yes! I rarely need to use operation queues and so forth myself, yet blocks are one of my favorite new features for developers in Mac OS X 10.61 and I can't wait to use them in iOS 4.0. Let's take a moment to review blocks before we get to the examples.

Blocks are an Apple extension to the C language. This means that you can use them not only in your Objective-C programs but in C and Objective-C++ as well. In computer science parlance blocks are known as lexical closures, but if that doesn't mean anything to you you can think of them as nameless functions with a very useful property: they capture variables. The truly powerful aspect of blocks however is that while they can be used in a pure C context, they are full-fledged Objective-C objects, meaning they can be copied, retained, and passed around just like any other object.

Block Basics with NSArray

Let's look at a simple way that blocks can make the NSArray class more powerful. Suppose we are adding a mapping method to NSArray by way of a category. Mapping is a staple of functional programming in which a new list is constructed by transforming each item in an existing list. Without blocks we would probably write something like this:

- (NSArray *)mapUsingSelector:(SEL)selector
{
	NSMutableArray *output = [NSMutableArray array];
	for (id obj in self)
		[output addObject:[obj performSelector:selector]];
	return [NSArray arrayWithArray:output];
}

Imagine that we want to use our new method to generate printable names from an array of Person class instances. Furthermore, the user should be able to specify one of two formats: FirstName LastName and LastName, FirstName. We can accomplish this with -mapUsingSelector: by adding two methods to Person:

- (NSString *)firstNameLastName;
- (NSString *)lastNameFirstName;

And then call -mapUsingSelector: in this fashion:

BOOL firstNameFirst = ...;
SEL selector = firstNameFirst ? @selector(firstNameLastName) 
                              : @selector(lastNameFirstName);
NSArray *names = [people mapUsingSelector:selector];

Not bad, but this requires a new method for each type of mapping we want to accomplish. We can do this by either modifying our model class, or by creating a category on the class. This works, of course, but it's not very graceful, and it has the undesirable side effect of breaking up the implementation of our feature into many places.

Now consider a mapping method written to take advantage of blocks:

- (NSArray *)mapUsingBlock:(id (^)(id obj))block
{
	NSMutableArray *output = [NSMutableArray array];
	for (id obj in self)
		[output addObject:block(obj)];
	return [NSArray arrayWithArray:output];
}

And here's how we use it to implement the list of names feature:

BOOL firstNameFirst = ...;
NSArray *names = [people mapUsingBlock:^(id person) {
	if (firstNameFirst)
		return [NSString stringWithFormat:@"%@ %@", 
		        [person firstName], [person lastName]];
	else
		return [NSString stringWithFormat:@"%@, %@", 
		        [person lastName], [person firstName]];
}];
A Few Words on Block Syntax

Block syntax can be somewhat daunting. Recall however that blocks are a C language extension and so they have been designed for that context. A block definition generally begins with the caret (^) symbol, followed by the parameters to the block, much like a function call. The body of the block is within the curly brackets. Blocks have a return type, but as a convenience the compiler will allow you to leave it off when the return type is unambiguous. In this case the return type of the block is id, so we can simply return an NSString object.

There are several advantages to our blocks-based approach:

  1. All of the code for this feature is in one place, which makes maintenance easier, and it makes the purpose of this section of code very clear to an observer.
  2. We can take advantage of compile time type-checking, and not run the risk of mistyping a selector name.
  3. There is no need for the components of our mapping function to comply with a specific message signature.
  4. We can use any number of variables to influence how the mapping is performed.

You may have noticed in the above example that we used the variable firstNameFirst from within the block. Blocks capture the value of referenced variables that are within scope: whenever a block is copied, those values are copied too. This is great for using arbitrary data inside of a block without having to pass arguments in, particularly in the case of block uses like NSArray's -sortedArrayUsingComparator: where the block parameters are predefined.

There is one wrinkle to using variables external to the block, however: by default they are considered const and cannot be modified (static and global variables are an exception to this). As such the following code produces a compiler error:

int matching = 0;
[objects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
	if (/* condition */)
		matching++; // Error since matching is const within the block!
}];

By adding the __block storage type modifier we can make this code snippet work:

__block int matching = 0;
[objects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
	if (/* condition */)
		matching++; // Can modify matching due to __block modifier.
}];

Note that you can still interact with objects in scope from within the block, such as modifying an NSMutableArray, but you cannot change the pointer itself without the __block modifier. Also, allowing variables to be modified entails significant overhead, so you should only use this modifier when necessary.

There is more to be learned about blocks, but now that we have a good idea of what blocks can and can't do, let's look at a few ways to use them.

BNRBlockView

Frequently in Mac and iPhone development there is a need for custom views to create a certain aesthetic. Imagine that you wanted to display a rounded rectangle in a view for your Mac or iPhone application. You would need to create an NSView/UIView subclass and implement -drawRect:. This requires two new files (the .h and .m files) and generally adds to the clutter of your project, particularly if you need to create a number of simple custom views.

Wouldn't it be great if you could define a custom-drawn NSView subclass on the fly and add it to the view hierarchy? By using blocks to create a reusable class, BNRBlockView, we can make this possible. Consider this example usage within an NSViewController subclass:

- (void)awakeFromNib
{
	__block __typeof__(self) blockSelf = self;
	[[self view] addSubview:[BNRBlockView viewWithFrame:contentRect
	                                             opaque:NO
	                                    drawnUsingBlock:^(BNRBlockView *view,
	                                                      NSRect dirtyRect)
	{
		[[blockSelf roundedRectFillColor] set];
		[[NSBezierPath bezierPathWithRoundedRect:[view bounds]
                                            xRadius:5
                                            yRadius:5] fill];
	}]];
}

Aside from the general coolness of being able to define a custom-drawn view inline like this, note that because the block captures the value of self we can also avoid a lot of extra code such as defining accessors for color properties and so forth.

Let's look at the source for BNRBlockView, which could easily be adapted for iPhone use. First, BNRBlockView.h:

@class BNRBlockView;
// Declare the BNRBlockViewDrawer block type:
typedef void(^BNRBlockViewDrawer)(BNRBlockView *view, NSRect dirtyRect);
@interface BNRBlockView : NSView {
	BNRBlockViewDrawer drawBlock;
	BOOL opaque;
}
+ (BNRBlockView *)viewWithFrame:(NSRect)frame
                         opaque:(BOOL)opaque
                drawnUsingBlock:(BNRBlockViewDrawer)drawBlock;
@property (nonatomic, copy) BNRBlockViewDrawer drawBlock;
@property (nonatomic, assign) BOOL opaque;
@end

And BNRBlockView.m:

@implementation BNRBlockView
@synthesize drawBlock, opaque;
+ (BNRBlockView *)viewWithFrame:(NSRect)frame 
                         opaque:(BOOL)opaque
                drawnUsingBlock:(BNRBlockViewDrawer)theDrawBlock
{
	__typeof__(self) *view = [[self alloc] initWithFrame:frame];
	[view setDrawBlock:theDrawBlock];
	[view setOpaque:opaque];
	return [view autorelease];
}
- (void)dealloc {
	[self setDrawBlock:nil];
	[super dealloc];
}
- (void)drawRect:(NSRect)dirtyRect {
	if (drawBlock)
		drawBlock(self, dirtyRect);
}
- (BOOL)isOpaque {
	return opaque;
}
@end

It's important to point out that the drawBlock block/property is copied, not retained. We copy the block because initially it exists on the stack and so retaining it is not enough to prevent it from disappearing on us when the calling method's stack frame is removed.

You may have noticed in the -awakeFromNib implementation that we created a special copy of self:

__block __typeof__(self) blockSelf = self;

This is to prevent a retain cycle, which we would run into if we used self within the block:

  • Our superview retains the BNRBlockView subview.
  • BNRBlockView retains drawBlock.
  • drawBlock retains a reference to self, i.e., the superview.

By assigning self to a special __block variable we can prevent the block from capturing (retaining) self, as __block variables are not auto-retained by blocks.

Mixing Background Work with Main Thread UI Interaction

Even the most simple applications sometimes need to perform a time-intensive blocking operation. Perhaps you need to process some data during your application startup, or upload a photo. In order to keep the application responsive such operations should be performed in a background thread. However, when it comes time to notify the user that the operation has completed we need to be on the main thread.

The most common way to accomplish this is usually with a call to -performSelectorInBackground:withObject: with calls to -performSelectorOnMainThread:withObject:waitUntilDone: to perform the updates. This is a fine approach, but it has some significant downsides. It requires cluttering up your class with two (or more) additional methods, and more critically, just like in the sorting example above, any state information you want to pass between threads must either be encapsulated in an object (to be conveyed using the withObject: parameter), or stored as an instance variable.

With the help of a simple NSThread category, part of which was adapted from a post by Landon Fuller), we can employ a much more elegant and reusable solution to this problem:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	
	NSString *token = ...;
	
	[statusLabel setStringValue:@"Processing..."];
	[NSThread performBlockInBackground:^{
		
		NSString *results = PerformMyTimeConsumingOperation(token);
		
		[[NSThread mainThread] performBlock:^{
			[statusLabel setStringValue:@"Processing completed!"];
			[resultsView setResults:results];
		}];
	}];
}

By nesting our blocks in this fashion we keep the sequence of operations very clear, and we keep the code related to this process in close proximity, regardless of what thread it needs to run in.

Here is the source code for the above categories:

@interface NSThread (BlocksAdditions)
- (void)performBlock:(void (^)())block;
- (void)performBlock:(void (^)())block waitUntilDone:(BOOL)wait;
+ (void)performBlockInBackground:(void (^)())block;
@end
@implementation NSThread (BlocksAdditions)
- (void)performBlock:(void (^)())block
{
	if ([[NSThread currentThread] isEqual:self])
		block();
	else
		[self performBlock:block waitUntilDone:NO];
}
- (void)performBlock:(void (^)())block waitUntilDone:(BOOL)wait
{
    [NSThread performSelector:@selector(ng_runBlock:)
                     onThread:self
                   withObject:[[block copy] autorelease]
                waitUntilDone:wait];
}
+ (void)ng_runBlock:(void (^)())block
{
	block();
}
+ (void)performBlockInBackground:(void (^)())block
{
	[NSThread performSelectorInBackground:@selector(ng_runBlock:)
	                           withObject:[[block copy] autorelease]];
}
@end

Completion Blocks

A similar technique can be applied to make managing sheets and alerts easier. Apple has already taken a step in this direction with NSSavePanel:

- (void)beginSheetModalForWindow:(NSWindow *)window 
               completionHandler:(void (^)(NSInteger result))handler

Using the above NSThread category and this message prototype as a template, try creating your own NSApplication or NSAlert category for displaying modal sheets and alerts to the user.

Blocks as an Alternative to Fast Enumeration

Enumerating objects is a common activity. If your application or framework implements its own collection objects, or if you have a need to process data serially (such as with large chunks of data), you might have considered Objective-C 2.0's fast enumeration as an approach: By implementing the NSFastEnumeration protocol your collection objects can support the for...in construct. However, implementing NSFastEnumeration is not trivial, and in some cases it may not serve the interests of the application.

Blocks, however, provide a very clear means to iterate over collections of objects, even as they are being read or processed. This helps to keep an application's memory footprint small, as we do not have to load the entire collection of objects into memory in order to process them. We can also supply more state information, such as the index of the object, reducing the need to encapsulate multiple objects and eliminating boxing of scalar values.

Big Nerd Ranch's own BNRPersistence implements a blocks-based enumeration method. Here's how it's used:

BNRStore *store = ...;
[store enumerateAllObjectsForClass:[BNRGuide class] 
                        usingBlock:^(UInt32 rowID, BNRStoredObject *obj, BOOL *stop) 
{
	BNRGuide *guide = (BNRArticle *)obj;
	[guide generatePdfAtUrl:...];
}];

The implementation is pretty straightforward. First, we define a type, BNRStoredObjectIterBlock:

typedef void(^BNRStoredObjectIterBlock)(UInt32 rowID, 
                                        BNRStoredObject *object,
                                        BOOL *stop);

Then we implement enumerateAllObjectsForClass:usingBlock: (simplified for brevity)

- (void)enumerateAllObjectsForClass:(Class)c 
                         usingBlock:(BNRStoredObjectIterBlock)iterBlock
{
	BNRBackendCursor *const cursor = [backend cursorForClass:c];
	BNRDataBuffer *const buffer = ...;
	
	UInt32 rowID;
	while (rowID = [cursor nextBuffer:buffer])
	{
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		
		BNRStoredObject *obj = [self objectForClass:c
		                                      rowID:rowID
		                               fetchContent:NO];
		[obj readContentFromBuffer:buffer];
		
		BOOL stop = NO;
		iterBlock(rowID, obj, &stop);
		
		[pool drain];
		
		if (stop)
			break;
	}
}

Note that we create an NSAutoreleasePool as we iterate over each object in order to keep our memory usage low.

Wrap-Up

I hope I've been able to impart some of my enthusiasm for blocks to you, and maybe you're beginning to think of ways you can use blocks in your own applications. They're an extremely versatile tool, and the increasing inclusion of blocks in Apple's APIs as well as the ability to augment common classes with useful categories make developing for Mac OS X and iOS that much more efficient and fun.

1. Blocks are also available for Mac OS X 10.5 and iPhone OS 2.2 and up in the form of PLBlocks, an open source project.

Want an answer to your Mac programming question? Submit your question to www.informit.com/askbnr.

Comments

comments powered by Disqus

Become an InformIT Member

Take advantage of special member promotions, everyday discounts, quick access to saved content, and more! Join Today.