Introducing Blocks - Learning iOS Development: A Hands-on Guide to the Fundamentals of iOS Programming (2014)

Learning iOS Development: A Hands-on Guide to the Fundamentals of iOS Programming (2014)

Chapter 13. Introducing Blocks

Blocks are one of the most powerful and underutilized features of Objective-C. It is important that you understand blocks as more system calls start taking them as arguments. Blocks are part function, part method, and part variable. For many, blocks can appear magical, especially the way they seem to pull in the variables around them.

This chapter introduces you to blocks and helps you avoid the most common mistakes. You start by learning the basics of blocks: what they are and how to declare, use, and write them. Then you use your understanding of blocks to make the About taxi more inviting.

Next, you learn more about blocks, including other variables, their most powerful feature and the source of most mistakes. Then you see how to change those variables, not just read them. Finally, you integrate everything and replace part of a protocol. As you learn in this chapter, blocks give you a versatile way to share data and information between objects.

Block Basics

In many ways, a block is like an anonymous function: It can take arguments, it can return a value, and a symbol can be used to call a block. But blocks are much more than this. Using them is a way to pass inline “functions” as method arguments. And most powerfully of all, blocks have access to all the variables that are in scope at the time they are defined. Or put another way, a block is a closure that has access to any variable the creating method can access at the point when the block is defined.

Before you can use blocks, you need to know how to declare, use, and write them.

Declaring Blocks

Blocks use a modified function syntax. Like functions, blocks can take arguments and return values. This is the basic format of a declaration:

<ReturnType>(^<BlockName>)<(Arguments)>

Image ReturnType is the variable type returned by a block, if any.

Image BlockName is the symbol used for calling the block. The caret (^) is the system-wide indicator preceding the declaration of a block.

Image Arguments is an optional list of arguments.

Here are a few examples of block declarations:

Image void (^simpleBlock)(void);

This is a simple block with no return value and no arguments.

Image NSString* (^returnsAStringBlock)(void);

This block returns a string and takes no arguments.

Image BOOL (^returnsABoolAndTakesTwoInts) (int first, int second);

This more complex block takes two integer arguments and returns a Boolean.

These declarations are used for blocks as variables or properties. You use a slightly different form for arguments to methods:

Image - (void)method1:(void (^) (void)) aSimpleBlock;

Image - (void)method2:(NSString* (^) (void))aBlockReturningAString;

Image - (void)method3:(BOOL (^)
(int first, int second))blockTakesTwoIntsAndReturnsABool;

Using Blocks

You call blocks in much the same way you call functions. For a named block, this works just as you would expect:

<ReturnVariable> = <BlockName>(<Arguments>);

Image ReturnVariable is an optional variable for holding the value returned by a block. If the block has no return value, you omit both ReturnVariable and the equals sign.

Image BlockName is the symbol used for calling the block.

Image Arguments is an optional list of arguments.

Note that a caret is not used for calling a block. It is used only for declaring and writing. For each of the blocks defined in the preceding section, here is an example of a call:

Image simpleBlock();

Image NSString* returnString = returnsAStringBlock();

Image BOOL isEqual = returnsABoolAndTakesTwoInts(1,2);

Writing Blocks

Writing really has two parts: the return type and argument definitions, and the code for implementing its behavior. This is the definition format:

^(<ReturnType>)(<Arguments>){<Statements>};

Image ReturnType is the variable type returned by a block, if any.

Image Arguments is an optional list of arguments.

Image Statements is the code implementing the behavior and can be anything you would normally write in Objective-C.

There is no block name in the implementation. Usually, you declare a block and assign it by using the definition. Here are the previous declarations, with sample block definitions:

Image

void(^simpleBlock)(void) =
^void(void){NSLog(@"simpleBlock called");};

Image

NSString*(^returnsAStringBlock)(void) =
^(NSString*)(void){return @"Here is a string";};

Image

BOOL(^returnsABoolAndTakesTwoInts) (int first, int second) =
^BOOL(int first, int second){return (first == second);};

Some of the definitions seem a bit verbose, especially all the voids. There are two space savers you can use for defining blocks. First, you can omit arguments if there are none. That cuts out a little from the first two block examples. Second, the compiler figures out the return type by using the definition. Therefore, the definitions can change to this:

Image

void(^simpleBlock)(void) =
^{NSLog(@"simpleBlock called");};

Image

NSString*(^returnsAStringBlock)(void) =
^({return @"Here is a string";};

Image

BOOL(^returnsABoolAndTakesTwoInts) (int first, int second) =
^(int first, int second){return first == second};};

Although the examples define and assign a block at the same time, the definition and assignment can happen in different places, even in different objects. This enables you to use blocks as arguments to methods or even class properties, providing lots of power and flexibility.

If you are the user of a class or method, you are not defining or using the block but are writing it:

[anObject aBlockBaseMethod:^{<your code goes here>}];

You have already done this with calls to dispatch_async in Chapter 5, “Localization,” and to dispatch_once to create a singleton in Chapter 11, “Navigation Controllers II: Split View and the iPad.” Both functions take a block of code and execute it either in the background or only once. They execute the code provided in a block.

If you look through the documentation, you see numerous examples of methods and system calls using blocks. One fun example is UIView animation.

Pulsing the About Taxi

In Chapter 12, “Touch Basics,” you added a draggable yellow taxi to the about view. But how does the user know to tap it? One way you can let the user know is to show some text on the screen inviting the user to tap on the car. But that is fairly glaring, and it clutters up the screen.

A better way is to subtly pulse the view and perhaps even add a bit of movement. Doing that is easy with UIView’s block-based animation calls. The calls do the hard work for you. If you want to rotate a view, you can tell the system how long the animation should take and specify the new rotation. The system figures out the interim animation steps and draws them on the screen.

You can animate many properties of view. However, when you use auto layout, modifying some properties can result in unexpected behavior. The solution is to animate the constraints. For more information, see Chapters 5, 6, and 7 of Auto Layout Demystified by Erica Sadun.

Pulsing and View Constraints

How does pulsing translate into view constraints? Pulsing makes a view slightly larger and then returns it to normal size, while the center of the view stays at the same location.

Growing changes the height and width constraints. Staying in the same place depends on the constraints that affect position. The taxi is horizontally centered, with a fixed space between the bottom of the taxi view and the top of the label. If you change only the height and width, but not the space, the view moves to maintain distance from the label. And that is not what you want. Instead, you need to change the distance as well as the width and height.

You can continue using the project you used in Chapter 12 or use CH13 CarValet Starter provided with the sample code. Adding pulsing requires three main steps:

1. Change the view constraints to something more animation-friendly and get references to the animated constraints.

2. Create an animation call for pulsing as well as required state variables.

3. Stop the view pulsing when it has been dragged or pause pulsing if the about view moves offscreen.

First, you need to set up animated constraints and references:

1. Open AboutViewController.xib, select the taxi view, and view the constraints in the Size inspector.

2. Create properties for the width and height constraints named labelTaxiSpace, taxiWidth, and taxiHeight.

The pulsing animation is a repeating cycle with two stages. Stage one is two relatively quick grow/shrink cycles. Stage two is a longer delay, and then the cycle repeats. This continues until either the user drags the view or the about view moves offscreen. Of course, moving offscreen is different from completely stopping the animation. Stopping only happens when the user taps the taxi. Moving offscreen is a pause, not a stop.

Follow these steps to create a pulsing taxi:

1. Add support for UIGestureRecognizerDelegate to AboutViewController.h.

2. Open AboutViewController.m and add these variables below the implementation:

{
BOOL draggedOnce;
BOOL paused;
NSInteger pulseCount;
}

draggedOnce is true after the user has touched the taxi. paused is true when the animation should be paused. And pulseCount keeps track of the short and long pulse stages.

3. Initialize the state variables in viewDidLoad (the new code is bold):

[super viewDidLoad];

draggedOnce = NO;
paused = NO;
pulseCount = 0;

DragViewGesture *dragView = [[DragViewGesture alloc] init];

Strictly speaking, you do not need to initialize Booleans to NO or integers to 0, as these are their default states. However, it is good practice to initialize in case you choose new starting values later.

4. Add the gesture recognizer delegate method at the end of the class to set draggedOnce to YES when the user drags the view:

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

if (!draggedOnce &&
[gestureRecognizer isKindOfClass:[DragViewGesture class]]) {
draggedOnce = YES;
}

return YES;
}

5. Start the animation in viewDidAppear: if the user has not dragged the taxi. Reset any state variables before starting the pulse:

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];

if (!draggedOnce) {
pulseCount = 0;
paused = NO;
[self pulseTaxi];
}
}

6. Pause the animation if the about view goes offscreen:

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];

paused = YES;
}

7. Finally, add the pulsing code from Listing 13-1.

Listing 13-1 Pulsing the Taxi View


#pragma mark - Pulse the Taxi

- (void)pulseTaxi {
if (!paused && !draggedOnce) { // 1

CGFloat pulseAmount = 6.0f; // 2
CGFloat spaceAmount = pulseAmount / 2.0f;

[UIView animateWithDuration:0.3f // 3
delay:0.0f
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.taxiWidth.constant += pulseAmount; // 4
self.taxiHeight.constant += pulseAmount;
self.labelTaxiSpace.constant -= spaceAmount;

[self.view layoutIfNeeded]; // 5

pulseCount++ ; // 6
}
completion:^(BOOL finished) { // 7
self.taxiWidth.constant -= pulseAmount; // 8
self.taxiHeight.constant -= pulseAmount;
self.labelTaxiSpace.constant += spaceAmount;

[self.view layoutIfNeeded];

if (!draggedOnce) { // 9
NSTimeInterval delay = 0.1f;

if (pulseCount > 1) { // 10
pulseCount = 0;

delay = 2.8f;
};

[self performSelector:@selector(pulseTaxi) // 11
withObject:nil
afterDelay:delay];

}
}];
}
}


Here’s what happens in the numbered lines in Listing 13-1:

1. Animate only if the view is visible, !paused, and the user has yet to drag the taxi, !draggedOnce.

2. Set the view growth amount to 6 points and the distance to the top of the label view to half that. Remember that the growth amount is the total for both taxi sides in the horizontal or vertical direction.

3. Set up the animation call. A duration of 0.3 seconds is the same as many iOS animations. There is no delay, and you want the user to be able to drag the view during an animation. UIViewAnimationOptionAllowUserInteraction lets the view receive touch events. Forgetting this option is a common source of errors as doing so prevents any type of interaction, including button presses.

4. This is the start of the animation block. Increasing the constant for width and height increases the size of the view. Similarly, decreasing the space constraint reduces the gap.

5. If you forget to tell the view to lay out subviews, the animation either does not occur or is very jumpy. This is another common source of errors when animating constraints.

6. Pulse count tracks the animation stage.

7. The completion block is called after the animation is done. Anything you do in this block happens after the initial animation.

8. Reset the constant of each of the three constraints back to their original values, returning the taxi view to its original size while keeping it centered.

9. Check whether the view has been dragged, and if it has not, continue pulsing.

10. The next pulse delay depends on whether this is a first or second pulse. If it is the first pulse, there is a short delay. Otherwise, there is a longer delay, setting up a kind of heartbeat look.

11. Call the pulse method again after the short or long delay.

Run the app in both the iPad Simulator and iPhone Simulator. Try opening the About screen, watching a few pulse cycles, and changing to a different screen in the middle of a pulse. Go back to About and make sure the pulse cycle continues. Then try dragging the taxi and make sure the pulsing stops. Try starting the drag when the taxi is in the middle of pulsing and make sure it returns to normal. As soon as the view pulses, it snaps back to the original position.

Variable Scope

In the taxi pulsing example, you might have noticed something strange about the animation block. It uses pulseAmount, spaceAmount, and self. But how? If this were a function, each of those would need to be an argument. But the block takes no arguments.

This is one of the very powerful features of blocks. They have access to all the variables at the point where they were created. That is, they pull in all the variables from the current scope. One way to think about this is that they have access to the same variables that a line of code in exactly the same position would have. If you have used languages like Smalltalk and Lisp, you can think of blocks as closures.

Try the following example by adding the code to viewDidLoad for any controller showing on the screen:

int aNumber = 8;
void (^testBlock)(void) = ^{NSLog(@"\naNumber = %d", aNumber);};
testBlock();

When you run the app, viewDidLoad is executed along with the code you added, and a result like this is printed to the console:

2013-06-02 15:49:17.518 CarValet[12359:c07]
aNumber = 8

Now add these two lines of code after the first block call:

aNumber = 99;
testBlock();

Before you run the code, what do you expect to print in the console? If you said 99, you are in for a surprise and have been caught by the most common mistake made in using blocks.

Copying and Modification

You may have expected to see 99 printed in the console because in the pulsing example, you were able to successfully modify self.taxiWidth.constant. This has to do with how the block’s execution stack is created. Figure 13-1 shows that the stack has a read-only (italicized) copy of each variable. The values are frozen at the time the block is created.

Image

Figure 13-1 Block context variables

Although the block’s copy of self is not modifiable, the value is still the address for the same aboutViewController object. The properties can still be changed, as can any part of those properties.

For the 99 example, aNumber is not a pointer; the value of the memory address is the value of the variable. The block’s copy of aNumber is at a different address from the one outside the block. The value of this new variable is identical to the value of aNumber at the time the block was created. But since it is not a pointer, it is just a copy of the integer, not the actual variable from the method.

Modifying Scope Variables

Blocks are not able to modify the copied, or scoped, variables available to them. You can easily see this by changing the block to modify aNumber:

void (^testBlock)(void) = ^{aNumber = 5; NSLog(@"\naNumber = %d", aNumber);};

You get an error in the editor, and if you try to build, it fails with “Variable is not assignable.”

If you need to modify a local variable inside a block, you need to add the __block modifier to the declaration. Change the declaration of aNumber to this:

__block int aNumber;

The error for the block disappears. More than that, changes to aNumber outside the block are reflected inside the block. Now execute this:

__block int aNumber = 8;
void (^testBlock)(void) = ^{NSLog(@"\naNumber = %d", aNumber);};
testBlock();
aNumber = 99;
testBlock();

The app prints the following at the console:

2013-06-02 16:17:21.936 CarValet[12647:c07]
aNumber = 8
2013-06-02 16:17:21.937 CarValet[12647:c07]
aNumber = 99

There are other issues around copying and not copying variables and objects into blocks, as well as more detail on how to create and use them. For more information, see Chapter 17 of Learning Objective-C by Robert Clair.

In the last part of this chapter, you practice what you have learned and replace a protocol with blocks.

Replacing a Protocol

Protocols enable one object to communicate with another, without either object knowing anything about the internals of the other. As you have seen, a protocol specifies a contract that the delegate implements the required methods conforming to the specification.

Like a protocol, a block is a contract. One object defines the return value and arguments. The other object writes a block matching the specification.

You have already done this in pulsing the taxi view. The animation call takes two defined blocks. One is a simple block with no arguments and no return value. The other takes one argument. AboutViewController implements the blocks as part of the method call, although you could just as easily define and write them elsewhere and use the block names in the method call.

In this example, you replace ViewCarProtocol with blocks. Note that a protocol can be more flexible than a single block. Protocols can have multiple methods for different purposes. They can also set methods as required or optional. A delegate declaring support for a protocol must support all the required methods, or the compiler generates an error. With blocks, you only know that implemented blocks conform, not that all required blocks are provided.

You replace the protocol in four general steps:

1. Update ViewCarTableViewController to use blocks instead of the protocol.

2. Modify CarTableViewController to set the new blocks.

3. Change CarDetailViewController to use blocks instead of the protocol.

4. Modify MainMenuViewController to set up blocks.

Step 1: Changing ViewCarTableViewController

Starting with the .h file, follow these steps:

1. Delete the import of ViewCarProtocol.h.

2. Change the delegate to UIViewController *delegate.

3. Add these block properties:

@property (copy, nonatomic) CDCar* (^carToView)(void);
@property (copy, nonatomic) void (^carViewDone)(BOOL dataChanged);
@property (copy, nonatomic) void (^nextOrPreviousCar)(BOOL isNext);

One thing to notice is that each block property is marked as copy, not strong. This is very important and is related to how blocks work with external variables. Again, for more information, see Chapter 17 of Learning Objective-C by Robert Clair.

Each of the properties declares a block that replaces the same protocol method:

Image carToView returns a CDCar*.

Image carViewDone takes one argument indicating whether the data has changed.

Image nextOrPreviousCar takes a Boolean that indicates which way to move.

The next step is calling the blocks. The blocks are properties, so you need to get a reference to make the call:

self.<blockSymbol><Arguments>;

You could use the underscore version of the symbol name and skip using dot notation. However, if you do, you lose the flexibility of updating blocks dynamically.

Change ViewCarTableViewController.m to call blocks:

1. Update navigationController:didShowViewController:animated: with the new code shown in bold:

if (viewController == self.delegate) {
if (dataUpdated) {
self.carViewDone(dataUpdated);
}

self.navigationController.delegate = nil;
}

2. Change the body of swipeCarLeft as follows:

self.carViewDone(dataUpdated);

self.nextOrPreviousCar(YES);

[self loadCarData];

3. Make similar changes to the body of swipeCarRight:

self.carViewDone(dataUpdated);

self.nextOrPreviousCar(NO);

[self loadCarData];

4. Make one change in loadCarData:

dataUpdated = NO;

self.myCar = self.carToView();

self.makeLabel.text = self.myCar.make;

Most of the changes replace delegate method calls with block calls. There is still a delegate, but now it is only used to check whether the detail car view is returning to the cars table.

Step 2: Updating CarTableViewController

The changes to CarTableViewController are quite small: removing the protocol, making the existing protocol methods public, and setting the ViewCarTableViewController block properties.

To save time, each of the blocks is a call to the original protocol method. If you were starting the design from scratch, you would do the work in the blocks instead of calling methods. Here’s what you do:

1. Open CarTableViewController.h and remove all references to ViewCarProtocol.

2. Add public declarations for each of the old protocol implementation methods:

- (CDCar *)carToView;
- (void)carViewDone:(BOOL)dataChanged;
- (void)nextOrPreviousCar:(BOOL)isNext;

At the moment, MainMenuViewController is the only caller for one of the methods. The declarations are there for flexibility. Again, if this were designed from scratch, you could create a better solution.

3. Open the .m file and insert the following lines in prepareForSegue:sender:

nextController.carToView = ^{return [self carToView];};
nextController.carViewDone = ^(BOOL dataUpdated)
{[self carViewDone:dataUpdated];};
nextController.nextOrPreviousCar = ^(BOOL isNext)
{[self nextOrPreviousCar:isNext];};
nextController.delegate = self;

The biggest change is in step 3. Here you set the block properties using simple calls to the existing methods. You could just replace the body of the block with the body of each method. For instance, this could be the carToView property:

nextController.carToView = ^{
currentViewCarPath = [currentTableView indexPathForSelectedRow];

return [fetchedResultsController objectAtIndexPath:currentViewCarPath];
};

Step 3: Modifying CarDetailViewController

Very few changes are needed for this class. Follow these steps:

1. In the .h file, remove all references to ViewCarProtocol and delete the delegate declaration.

2. Add a declaration for the nextOrPrevious: block property:

@property (copy, nonatomic) void (^nextOrPreviousCar)(BOOL isNext);

3. In the .m file, use the new block in swipeCarRight: and swipeCarLeft:. Each call looks like this:

self.nextOrPreviousCar(<BooleanArgument>);

Replace BooleanArgument with either YES or NO, depending on the method.

All that remains is to update the iPad’s main car menu.

Step 4: Updating MainMenuViewController

This class only uses the protocol for going to the next or previous car, so there are two simple updates:

1. In the .h file, remove the two uses of ViewCarProtocol.

2. In the .m file, tableView:didSelectRowAtIndexPath: has a small change. In the switch case for kPadMenuCarsItem, replace the line:

currentCarDetailController.delegate = carTable;

with this:

currentCarDetailController.nextOrPreviousCar = ^(BOOL isNext)
{[carTable nextOrPreviousCar:isNext];};

This change uses blocks copying in scope variables to include carTable, a reference to the master cars table menu. Since carTable implements nextOrPreviousCar:, and the copy points to the same object, it is used to call the method.

Run the app using the iPhone Simulator and the iPad Simulator, and make sure all the behaviors previously implemented by ViewCarProtocol still work. On iPhone, this includes displaying the correct car detail, updating the cars table when some detail changes, and going to the next and previous cars. On iPad, it is just the next and previous swipes.

With blocks, there is no need to create lots of similar protocols or use a protocol that doesn’t quite match what you need. Providers don’t need to check if a delegate has implemented optional methods. For classes that would use a protocol, instead of registering as a delegate and creating essentially empty protocol methods, you only create code for the things you need.

By replacing the ViewCarProtocol with blocks, you have started using a new and more versatile way to exchange information and trigger behaviors. This method is increasingly being adopted by the native iOS calls.

Summary

In this chapter, you have gotten an introduction to blocks. You started by learning how to declare, use, and write blocks. You also learned some shortcuts for writing blocks. Next, you added pulsing to the About screen’s taxi and learned about animating view constraints.

Next, you learned about variable scope, one of the most powerful features of blocks. Then you learned about read-only versus read/write variables and why you can change parts of a copied object. Finally, you replaced the ViewCarProtocol with blocks.

Now that you understand blocks, you can use them for more than just arguments to system calls. Blocks provide an easy and powerful way to pass information and actions between objects of the same class or different classes. You can use them instead of protocols to increase the flexibility and reusability of your code. And as you go deeper into iOS, using blocks is a natural way to create small asynchronous actions.

There are just two chapters left before you are ready to launch into designing and building your own apps. No matter what apps you choose to build, there are two constants. The first is the need to tune performance. Instruments is an easy-to-use tool to find memory problems and make sure your app uses the limited resources in the best way. The other constant is that bugs happen. Chapter 14, “Instruments and Debugging,” introduces you to Instruments, then takes you to the next level with the debugger. In addition to seeing some techniques to track down common issues, you learn about the versatility of breakpoints.

Challenges

1. Add movement as well as pulsing to the about view taxi. The movement should happen during the pulse to suggest that a tap will move the taxi. You can use the existing labelTaxiSpace constraint or introduce others to give the taxi more of a wobble.

2. setupScrollContent in CarImageViewController.m contains a for loop that iterates through image names:

for (NSString *atCarImageName in carImageNames) {

Replace the loop using the array block iterator enumerateObjectsUsingBlock:. The first few lines replacing the for loop look like this:

[carImageNames enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop) {
carImage = [UIImage imageNamed:obj];

You will also need to make other changes to make the block work.

3. Replace YearEditProtocol with blocks.