iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (89 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
6.01Mb size Format: txt, pdf, ePub
Typical Block Usage

In this chapter, our examples have been pretty trivial. In the next chapter, you will see some real world examples of blocks and see just how useful they can be. However, to whet your appetite a bit, let’s talk about how blocks are typically used.

 

Most commonly, blocks are used as callbacks. For example, in
Chapter 13
you supplied a block when dismissing a modal view controller. When the dismissal completed, the block was executed. Thus, you can think of blocks as a one-shot callback for an event to occur some time in the future. We usually call a block used in this manner a
completion block
.

 

Using a completion block is usually much simpler than setting up a delegate or a target-action pair. You don’t have to declare or define a method or even have an object to set up the callback, you just create a block before the event and let it fire. A block, then, is an object-less callback, whereas every other callback mechanism we’ve used so far (delegation, target-actions, notifications) must have an object to send a message to.

 

Blocks are used in a lot of system APIs. For instance,
NSArray
has a method named
sortedArrayUsingComparator:
. When this message is sent, the array uses the block to compare every object it holds and returns another array that has been sorted. It looks like this:

 
NSArray *sorted = [array sortedArrayUsingComparator:
^NSComparisonResult(id obj1, id obj2) {
    if ([obj1 value] < [obj2 value])
        return NSOrderedDescending;
    else if ([obj1 value] > [obj2 value])
        return NSOrderedAscending;
    return NSOrderedSame;
}];
 

When working with a method that takes a block, you must supply a block that matches the signature declared by the method. For one thing, the compiler will give you an error if you supply a different kind of block. But also, the block has a specific signature for a reason: the method that uses it will expect it to work in a certain way. You can think of blocks used in this way as a

plug-in.

NSArray
says,

Hey, I got these objects, and I can feed them to your block to figure out which ones go where. Just give me the specifics.

 

There are many other ways to use blocks, like queuing up tasks (a block that calls a block that calls another block), and some objects that use blocks know how to spread the execution of them among different CPU cores. People have done some really clever things with blocks, and in the next chapter, we will show you one of those clever uses.

 
For the More Curious: The __block Modifier, Abbreviated Syntax, and Memory

Blocks have a few more options than we’ve talked about in this chapter. First, when allocating blocks we have always put the return type in the block signature:

 
^
returnType
(...) {
}

However, this isn’t necessary. The compiler can figure out the return type based on the
return
statement in the block.

 

For example, these are all valid blocks and assignments:

 
int (^block)(void) = ^(void) {
    return 10;
};
NSString * (^block)(void) = ^(void) {
    return [NSString stringWithString:@"Hey"];
};
void (^block)(void) = ^(void) {
    NSLog(@"Not gonna return anything...");
};
 

Also, if a block doesn’t take arguments, you don’t need to include the argument list in the block literal. So, you could write a block and its assignment like this:

 
void (^block)(void) = ^{
    NSLog(@"I'm a silly block.");
};

The return type and empty argument list can only be omitted from the block literal. A block variable must have all of the information about the block it points to.

 

Earlier in this chapter, you saw how a block captured a variable by copying its value into its own memory. Subsequent changes to that variable didn’t affect the block’s copy. One thing we did not mention was that this captured variable cannot be changed within the block. For example, this is illegal:

 
int five = 5;
void (^block)(void) = ^{
    five = 6; // This line causes an error
};
 

Sometimes, you do want to modify a variable inside a block. A typical example is some sort of counter variable that should go up each time you execute a block. If you want this behavior, you have to specify the variable as mutable within the block by decorating it with
__block
:

 
__block int counter = 0;
void (^block)(void) = ^{
    counter++;
    NSLog(@"Counter now at %d", counter);
};
block(); // prints 1
block(); // prints 2
 

When a variable is decorated with
__block
, it actually lives in a special spot in memory: it is not a local stack variable, nor is it stored inside the block itself (
Figure 27.5
).

 

Figure 27.5  Shared Memory for __block variables

 

This is important because it means more than one block can modify that variable:

 
__block int counter = 0;
void (^plusOne)(void) = ^{
    counter++;
    NSLog(@"Counter now at %d", counter);
};
void (^plusTwo)(void) = ^{
    counter += 2;
    NSLog(@"Counter now at %d", counter);
};
plusOne(); // prints 1
plusTwo(); // prints 3
plusOne(); // prints 4
 

Additionally, if
counter
is modified after the block is defined, the value within the block is also changed:

 
__block int counter = 0;
void (^plusOne)(void) = ^{
    counter++;
    NSLog(@"Counter now at %d", counter);
};
counter = 25;
plusOne(); // prints 26
 

When capturing a pointer to an object, you can always change the properties of that object, but you cannot change what object the pointer points to. When declaring a pointer with the
__block
modifier, you can change the pointer to point at another object within the block:

 
NSString *string1 = @"Bar";
__block NSString *string2 = @"Bar";
void (^block)(void) = ^{
    string1 = @"Foo"; // This is ILLEGAL
    string2 = @"Foo"; // This is OK
};
 

Finally, one last tidbit of knowledge. When you allocate a block, it is created on the stack. This means that, even if you were to keep a strong reference to it, calling it later would result in a crash because the memory would be destroyed as soon as you leave the method in which it was defined.

 

The way around this disaster is to send the message
copy
to a block. When you copy a block, the block is moved to the heap, where storage outlasts the current stack frame. (You probably noticed that the
equation
property of
BNRExecutor
in this exercise had the
copy
attribute. That does the same thing.) If a block is already in the heap, sending it the message
copy
just adds another strong reference to it instead of making another copy of the block.

 

Before ARC, forgetting to copy a block was a big deal and created hard-to-find bugs when a block had a strong reference but had also been deallocated. With the advent of ARC, the compiler now knows a lot about blocks. So, even if you just assign a block to a strong variable or use a strong property, the block is copied to the heap for you if it is not already there. However, we still like to use
copy
for all of our block properties because it makes us feel in charge and all warm inside.

 

Now, you might be wondering,

If they didn’t tell me that blocks are automatically copied when using a strong property, how would I ever know that?

Well, when ARC first came out there was a little chatter in the release notes about it. However, the documentation wasn’t updated (and as of this writing, still isn’t updated) to reflect this special block copying procedure. But the neat thing about blocks is that they are a feature of the Objective-C language, which is not owned by anyone. Thus, you can look at the source for the Objective-C language. Apple maintains their version of Objective-C and a number of other open source libraries at
http://www.opensource.apple.com
.

 
For the More Curious: Pros and Cons of Callback Options

A callback, as you may remember from
Chapter 4
, is a chunk of code that you supply in advance of an event occurring. When that event goes down, the chunk of code gets executed. In this chapter, you have seen that blocks can be used as callbacks. Other approaches to callbacks you have seen in this book are delegation, target-action pairs, and notifications. Each one has benefits and drawbacks compared to the others. This section will expand on these benefits and drawbacks so that you can pick the appropriate one for your own implementations.

 

First, let’s note that each of these approaches to callbacks are design patterns that transcend their implementations in Cocoa Touch. For example, the target-action pair design pattern is implemented by
UIControl
, but this does not mean you have to use
UIControl
to use the design pattern. You could create your own class that kept a pointer to a target object and a
SEL
for the message that object would be sent when some event occurred.

 

A callback has two major components: the process of registering it and the code for the callback. When registering a callback using delegation, target-actions, or notifications, you register a pointer to an object. This is the object that will receive messages when events occur. Additionally, both target-actions and notifications require a
SEL
that will be the message that is sent to the object. Delegation, on the other hand, uses a pre-defined set of methods from a delegate protocol to regulate which messages get sent.

 

In these three callback design patterns, the code for the callback is in a distinct method implementation (
Figure 27.6
).

 

Figure 27.6  Callback design patterns

 

There are certain situations where one of these design patterns works better than the others.

 

Target-action
is used when you have a close relationship between the two objects (like a view controller and one of its views) and when there are many instances that call back. For example, a single interface controlled by one controller may have many buttons. If those buttons only knew how to send one message (e.g.,
buttonTapped:
), there would be mass confusion in the implementation of that method (

Uhhh... which button are you again?

). Each button having its own action message (e.g.,
redButtonTapped:
) makes life easier.

 

Delegation
is used when an object receives many events and it wants the same object to handle each of those events. You’ve seen many examples of delegation in this book because delegation is very common in Cocoa Touch. Delegation uses a protocol that defines all of the messages that will be sent, so you do not have control over the names of these methods, but you do not have to register them individually like with target-action pairs, either.

 

Notifications
are used when you want multiple objects to invoke their callback for the same event and/or when two objects are not related. Consider an application that has two view controllers in a tab bar controller. They don’t have pointers to each other, unlike two view controllers in a navigation controller stack, but one of them is interested in what is going on in the other. Instead of giving them pointers to each other, which can be messy for a variety of reasons, one of view controllers can post notifications to the notification center. The other could register as an observer for that type of notification. Similarly, another view controller could come along and register for the same notification, and both observers would get updated.

 

Blocks are the outliers when it comes to callbacks because they are not an object-oriented approach. Blocks are useful when the callback is going to happen only once or when the callback is just a quick and simple task (like updating a progress bar).

 

One of the reasons blocks are better suited for this one-shot behavior is that they will take ownership of any object they reference. If a block was to stay around forever, the objects it references would also stay around forever. Of course, you can destroy the block when it is no longer needed, but what if that block is owned by an object that the block references? The object owns the block, the block owns the object, and now you can’t destroy them without some extra work. Since blocks stick around for just the one event, they will own what they need until they no longer need it.

 

Another reason blocks are well-suited for this situation goes along with the reason why they are good for quick and simple tasks: you can define the block’s code at the same point where the block is registered as a callback. This keeps your code nice and clean.

 

An approach to callbacks we have not discussed is subclassing. In some languages, delegation is not feasible because of the structure of the language. In these languages, classes like
CLLocationManager
would be abstract classes – ones that were meant to be subclassed. Any time you wanted to use an instance of
CLLocationManager
, you would subclass it and write the code for what happens when an event occurs in the implementation of this subclass. The subclass would probably have an instance variable for the object it was going to tell about these events, and that object would declare and define these methods. This gets complicated and ugly pretty quickly, and in Cocoa Touch, you do not see this pattern because we have better alternatives.

 

Other books

With This Kiss: Part One by Eloisa James
Bloodhype by Alan Dean Foster
West of January by Dave Duncan
El caldero mágico by Lloyd Alexander