Understanding Blocks
Although delegation is extremely useful, it's not the only way to customize the behavior of a method or function.
Blocks are like traditional C functions in that blocks are small, self-contained units of code. They can be passed in as arguments of methods and functions and then used when they're needed to do some work. Like many programming topics, understanding block objects is easier when you use them, as you do in the previous section.
With iOS 4 and newer versions, a number of methods and functions of the system frameworks are starting to take blocks as parameters, including the following:
- Completion handlers
- Notification handlers
- Error handlers
- Enumeration
- View animation and transitions
- Sorting
Here you are using a block-based method to animate the car, but block objects also have a number of other uses, especially in Grand Central Dispatch and the NSOperationQueue class, the two recommended technologies for concurrent processing.
One of the values of using blocks is that you can access local variables, which you can't do in a function or a call back. You don't have to pass data around, and a block can modify variables to pass data back In addition, if you need to change something, there is no API to change, with its concomitant ripple effect. In the animation explained in the previous section, you passed a block as the argument to a method. You created the block inline, because there wasn't that much code, and that is often the way it is done. But sometimes it is easier to follow what is happening by declaring a block variable and passing that as the argument to the method. The declaration syntax, however, is similar to the standard syntax for function pointers, except that you use a caret (^) instead of an asterisk pointer (*).
If you look the animateWithDuration:animations:completion: in the UIView class reference, you see
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
Apple is now treating blocks as a primary design pattern, up there with inheritance and delegation, so don't be surprised to find blocks being used more and more.
So go through this slowly, and by the end, you'll be comfortable with blocks, despite the really weird syntax.
To start, the animations is defined as a block that has no parameters and no return value:
void (^)(void))animations
Completion is defined as a block that has no return value and takes a single Boolean argument parameter and no return value:
(void (^)(BOOL finished))completion
When you create a block inline, you just use the caret (^) operator to indicate the beginning of a block with the code enclosed within the normal braces. Hence the code you entered previously:
animations:^ { car.center = center; }
and
completion:^(BOOL finished){ [self rotate]; }
Although in this example you use blocks inline, you could also declare them like any other local variable, as you can see in Listing-3.
Listing-3: Using Declared Blocks
- (IBAction)testDrive:(id)sender { CGPoint center = CGPointMake(car.center.x, self.view.frame.origin.y + car.frame.size.height/2); void (^animation)() = ^(){ car.center = center; }; void (^completion)(BOOL) = ^(BOOL finished){ [self rotate]; }; [UIView animateWithDuration:3 animations:animation completion:completion]; }
Remember:
When you declare a block you use the caret (^) operator to indicate the beginning of a block with the code enclosed within the normal braces, and a semicolon to indicate the end of a block expression.
The declaration in Listing-3 is pretty much the same as you saw in the animateWithDuration:animations:completion: method declaration, except with the identifiers moved around a little. I have bolded both to make that a little easier to see:
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
Here, you are declaring two block variables by using the ^ operator, one with the name of animations that has no return value, and one with the name of completion that has no return value and takes BOOL as its single argument:
void (^animation)() void (^completion)(BOOL)
This is like any other variable declaration (int i = 1, for example), in which you follow the equal sign with its definition.
You use the ^ operator again to indicate the beginning of the block literal - the definition assigned to the block variable. The block literal includes argument names (finished) as well as the body (code) of the block and is terminated with a semicolon:
void (^animation)() = ^(){ car.center = center; }; void (^completion)(BOOL) = ^(BOOL finished){ [self rotate]; };
Although the code you add in Listing-2 creates the blocks inline, you should actually replace the testDrive method with the code in Listing-3. Inline blocks work fine for small blocks; for large blocks, however, find that declaring the block makes the code easier to follow.