Welcome to another Friday Q&A. This week I thought I would take fellow amoeboid Jeff Johnson's suggestion and talk about blocks in Objective-C.
The word "blocks" is kind of ambiguous, so to clarify, I'm not talking about the compound statement structure which has existed in C since the beginning of time. I'm talking about a new addition to the language being created by Apple which adds anonymous functions to the language.
[Note: since this chapter was written, Apple's blocks implementation has been made public and is now completely mature and usable. While this chapter remains relevant, good up-to-date documentation on blocks can now be found on developer.apple.com.]
Since they're not available to the public in finished form yet, the discussion is going to be a bit imprecise in terms of syntax. But since I mainly want to talk about what they will do for us and not the absolute precise details of how to type them out, that's not a big problem. First let's see how they look:
x
=
^
{
printf
(
"hello world
\n
"
);
}
That's a block. The funny caret before the braces is what distinguishes it from boring old compound statements. Now we can simply call this block like so:
x
();
And the resulting code will print "hello world". Now let's introduce a couple of parameters:
x
=
^
(
int
a
,
char
*
b
){
printf
(
"a is %d and b is %s"
,
a
,
b
);
}
And then we can call this just the way you'd think:
x
(
42
,
"fork!"
);
Now let's remove the parameters again:
int
a
=
42
;
char
*
b
=
"fork!"
;
x
=
^
{
printf
(
"a is %d and b is %s"
,
a
,
b
);
}
x
();
This illustrates one of the really interesting things about blocks: they can capture variables from their enclosing scope. This is not particularly interesting here (why didn't we just pass a and b when invoking it?) but it gets really interesting when we start passing the block around to other functions:
int
a
=
42
;
char
*
b
=
"fork!"
;
callblock
(
^
{
printf
(
"a is %d and b is %s"
,
a
,
b
);
});
When the callblock()
function calls that block, the block will still get access to our local variables a and b even though we never passed them to the function explicitly.
We're just about done with the basics of what blocks are. One more quick example, a block that returns a value:
x
=
^
(
int
n
){
return
n
+
1
;
};
printf
(
"%d
\n
"
,
x
(
2
));
This code will print "3". Note that there is no need to declare the type of the return value as the compiler can simply infer it from the return statement.
So what's the big deal? A major advantage of blocks is that they essentially allow you to write your own control structures in the language without having to alter the compiler. As one example, take the for(... in ...)
syntax that appeared in Leopard. This syntax is a wonderful addition to the language. Previously we had to write a bunch of code just to iterate over an array:
NSEnumerator
*
enumerator
=
[
array
objectEnumerator
];
id
obj
;
while
((
obj
=
[
enumerator
nextObject
]))
// finally we can do something with obj
And the new syntax cuts this down to a single line:
for
(
id
obj
in
array
)
Which is great. The only trouble is that we went years and years without it. We had to wait for Apple to add it for us. With blocks, no more! You don't get quite the same syntax, but you can get the same convenience with a method you wrote entirely yourself:
my_for
(
array
,
^
(
id
obj
){
/* loop body goes here */
});
Or in a perhaps slightly stranger but much more interesting object-oriented form:
[
array
do
:^
(
id
obj
){
/* loop body goes here */
}];
The implementation of the -do:
method is left up to the reader, but rest assured that it's relatively simple.
As another example, consider the @synchronized
directive. This could be redone using blocks too:
[
obj
synchronized:
^
{
/* this is protected by the lock */
}];
OK, you say, I get it, but what's the big deal? After all, for/in
and @synchronized
are already part of the language, why would you rewrite them?
Of course you wouldn't. That would be silly. Those examples serve only to illustrate the idea: that you can build your own control structures. But of course it's only interesting to build control structures that are new! So here are some ideas.
[[
NSFileHandle
fileHandleForReadingAtPath:
path
]
closeWhenDone:
^
(
NSFileHandle
*
handle
){
/* use handle here */
}];
newArray
=
[
existingArray
map:
^
(
id
obj
){
return
[
obj
stringByAppendingString:
@"suffix"
];
}];
newArray
=
[
existingArray
filter:
^
(
id
obj
){
return
[
obj
hasPrefix:
@"my"
];
}];
/* threaded code */
PerformOnMainThread
(
^
{
/* synchronized code */
});
/* more threaded code */
PerformWithDelay
(
5.0
,
^
{
/* will run 5 seconds later */
});
[
array
doParallelized:
^
(
id
obj
){
/* will get executed on all of your CPU cores at once */
}];
And many other examples abound.
Another place where blocks will make things much nicer is when dealing with callbacks. If you've ever written much Cocoa code you've probably had to write a sheet callback, and it's a pain in the ass. If you need to pass variables through to the other side then it gets really frustrating with code like this:
-
(
void
)
method
{
int
foo
;
NSString
*
bar
;
/* do some work with those variables */
NSDictionary
*
ctx
=
[[
NSDictionary
alloc
]
initWithObjectsAndKeys:
[
NSNumber
numberWithInt:
foo
],
@"foo"
,
bar
,
@"bar"
,
nil
];
[
NSApp
beginSheet:
sheet
modalForWindow:
window
modalDelegate:
self
didEndSelector:
@selector
(
methodSheetDidEnd:returnCode:contextInfo:
)
contextInfo:
ctx
];
}
-
(
void
)
methodSheetDidEnd:
(
NSWindow
*
)
sheet
returnCode:
(
int
)
code
contextInfo:
(
void
*
)
ctx
{
NSDictionary
*
ctxDict
=
ctx
;
[
ctxDict
autorelease
];
int
foo
=
[[
ctxDict
objectforKey:
@"foo"
]
intValue
];
NSString
*
bar
=
[
ctxDict
objectForKey:
@"bar"
];
/* do some more stuff with those variables */
}
Wow! What a pain that is. Since I removed all the stuff that does work, nearly everything that remains is just boilerplate. Horrible boilerplate whose only purpose is to tell the sheet who to call, and to pack up local information in a way that the sheet can give it back to you later on. Now let's imagine we were redoing this API using blocks and see how it would look:
-
(
void
)
method
{
int
foo
;
NSString
*
bar
;
/* do some work with those variables */
[
sheet
beginSheetModalForWindow:
window
didEndBlock:
^
(
int
code
){
/* do stuff with foo */
/* do stuff with bar */
/* do stuff with code, or sheet, or window, or anything */
}];
}
Isn't that great? All that horrible boilerplate just flies right out the window. Code flow suddenly becomes completely logical, you can read it top to bottom, and you can access any local variables you please.
Let's take another example, sorting an array with a custom comparison function using some variables that you pass in. NSArray has functionality for this, with the -sortedArrayUsingFunction:context:
method. The old-style code is annoying, and I'm not going to write it. It's much like the sheet method above. You have to define a separate function, way outside of your code where it's not really visible. You have to set up the context to pass into it. If you're passing more than one thing then you have to pass a dictionary (and unpack it) or a pointer to a struct. Now here's the blocks version of a custom comparator:
sorted
=
[
array
sortedArrayUsingBlock:
^
(
id
a
,
id
b
){
/* compare, use local variables to decide what to do, run wild */
}];
And that's all there is to it.
Callbacks are one of the most powerful things in C and Objective-C but in many situations their use can be extremely difficult and unnatural. Blocks promise to allow callbacks and custom control constructs to be created and used in a much more natural fashion.
So far I've only shown examples of using a blocks API, but how about creating one? Well, it's a little worse, but not much. The only problematic thing is that the syntax for declaring a block type is kind of ugly, as it's modeled after function pointer syntax. But it's not too bad, and the rest is nice and simple. For example, here's how you could write that -map:
method from above:
-
(
NSArray
*
)
map:
(
id
(
^
)(
id
))
block
{
// takes an id, returns an id
NSMutableArray
*
ret
=
[
NSMutableArray
array
];
for
(
id
obj
in
self
)
[
ret
addObject:
block
(
obj
)];
return
ret
;
}
Pretty straightforward, especially considering the power it gives us.
Information on Apple's implementation of blocks is still a bit sparse. Some more details can be found in a mailing list post to the Clang development list. For more purely conceptual ideas on how blocks can be used, check out the Smalltalk language, where blocks are used for virtually every control structure right down to if/then and basic loops. Here's hoping that blocks allow for some major changes in how we work on Snow Leopard!