Fun with Blocks
Sunday, March 11, 2012 at 12:50AM In last week's class when we were talking about blocks, one of the students asked if any methods ever took block arguments with return values. I couldn't think of any API calls off hand--but I also couldn't think of any reasons why it wouldn't be possible. In fact, I realized it could be used to easily map from one array to another.
So I created a quick proof-of-concept, using a category on NSArray with a single method:
- (NSArray*)mapUsingBlock:(id (^) (id object)) block {
NSMutableArray* newArray = [NSMutableArray arrayWithCapacity:[self count]];
for (id object in self) {
[newArray addObject:block(object)];
}
return [newArray copy];
}
This simply iterates over the array and passes each item from our array to our block. It then stores all the return values in a new array, which it returns.
I could then call the code as shown below:
NSArray* names = [NSArray arrayWithObjects: @"Bob", @"Sally", @"Mary", @"Jim", nil];
NSArray* allCaps = [names mapUsingBlock:(id)^(id object) {
return [object uppercaseString];
}];
NSLog(@"Names = %@", names);
NSLog(@"All Caps = %@", allCaps);
NSArray* pigLatin = [names mapUsingBlock:^id(id object) {
NSString* firstCharacter = [[object substringToIndex:1] lowercaseString];
NSString* restOfName = [[object substringFromIndex:1] capitalizedString];
return [NSString stringWithFormat:@"%@%@ay", restOfName, firstCharacter];
}];
NSLog(@"Pig Latin: %@", pigLatin);
NSArray* counts = [names mapUsingBlock:^id(id object) {
return [NSNumber numberWithInt:[object length]];
}];
NSLog(@"Counts: %@", counts);
id (^booBlock) (id object) = ^id(id object) {return @"Boo";};
NSArray* boo = [names mapUsingBlock:booBlock];
NSLog(@"Boo: %@", boo);
The first example block simply returns an all-caps version of its argument. The second somewhat naively converts its argument into pig latin. The third returns an NSNumber holding the length of its argument--showing that the new array does not need to contain the same type of object as the original array. Finally, the last block simply returns a string value--it doesn't use the block's argument at all.
These examples also shows a few different ways of declaring our block. The first three define the block in-line. The last example stores the block in a local variable before passing it into our method.
Calling this code generates the following output:
2012-03-11 00:11:17.841 ArrayMap[13029:403] Names = (
Bob,
Sally,
Mary,
Jim
)
2012-03-11 00:11:17.844 ArrayMap[13029:403] All Caps = (
BOB,
SALLY,
MARY,
JIM
)
2012-03-11 00:11:17.846 ArrayMap[13029:403] Pig Latin: (
Obbay,
Allysay,
Arymay,
Imjay
)
2012-03-11 00:11:17.848 ArrayMap[13029:403] Counts: (
3,
5,
4,
3
)
2012-03-11 00:11:17.848 ArrayMap[13029:403] Boo: (
Boo,
Boo,
Boo,
Boo
)
So far, so good. But, let's look at Objective-C's block syntax for a second. First off, when calling mapUsingBlock: there are (at least) two valid syntaxes for creating our block: (id)^(id object) or ^id(id object). Neither of these match the syntax used when defining the method: (id (^) (id object)) block. Additionally, all three of these are different from the syntax used to declare local block variables: id (^blockName) (id object).
Dont' get me wrong. I love blocks. Of course, I was first exposed to them (or lambdas, closures, or whatever name you may choose to call these constructs) in languages like Lisp, Smalltalk and Ruby. I think they're a great addition to Objective-C, and I generally prefer Objective-C APIs that use blocks. I mean, seriously. Could we get a block-based API for UIAlertView? That would be awesome.
I even find myself writing my own methods to consume blocks. And not just toy examples like this. I'm all in on this whole block thing--and it looks like Apple is too. This became especially clear with iOS 5. There are a number of features (iCloud and Twitter come to mind) that you just cannot use without blocks.
Having said all that, let's face the facts. The block syntax is ugly and inconsistent, and it often drives me crazy. I find it almost impossible to remember the correct syntax from one use to the next. The only saving grace is Xcode's autocompletion. It generally does a great job creating the proper syntax for block arguments when calling block-based methods. Which helps a lot when using block-based APIs. Still, if you start creating your own block-based methods, you're on your own.



