What You’ll Learn in This Hour
Comparing blocks to callbacks
Using typedef
and function pointers
Declaring blocks
Using blocks
Blocks are not new; they were a part of Smalltalk and other languages 30 or more years ago. (In some cases, they were referred to as closures for reasons that you see in this section.) They show up in Ruby, Python, and Lisp as well. For a variety of reasons, they were not implemented on OS X until Snow Leopard (10.6); they have been in iOS since version 4. Because of their long history, for many people using blocks on OS X or iOS, it is a matter of revisiting concepts they might know from other languages or even from long-ago basic programming courses.
Blocks take advantage of one of the most basic principles of computers in the twentieth century; computers store and manipulate digital data. And here is the critical principle: Digital data can represent data in the sense of numbers or text just as easily as it can represent computer instructions. It is all digital, and it is all managed in basically the same way.
GO TO Hour 22, “Grand Central Dispatch: Using Queues and Threading,” p. 283, for additional information on blocks.
A block is a chunk of code that can be assigned to a variable or property similarly to the way in which you would assign an int
or a struct
or an object to a variable or property. You need not assign the chunk of code to a variable or property; you can also reference it in an expression.
Whether assigned to a variable or property or used in an expression, a block can access variables that are defined at the time it is assigned or used. The closure terminology refers to the fact that the block closes around the values of variables at the time it is defined. In this way, those variables differ from arguments that could be passed into a function.
Blocks are frequently used in loops. They are also frequently used in framework methods in much the same way as callback functions have been used in C and its derivative languages. Methods such as enumerateObjectsUsingBlock:
in NSArray
let you pass in a block as an argument to be used in the enumeration. A similar method in NSSet
is used in the same way, whereas in NSDictionary
, enumerateKeysAndObjectsUsingBlock:
is available. Not to be outdone, NSString
has enumerateLinesUsingBlock:
.
Note: Finding More on Enumerate Methods Using Blocks
These classes and methods are described more fully later in this hour.
Blocks are similar in some ways to callback functions, which have long been used in C and Objective-C as well as other programming languages. Callbacks are often used in real-time processing, so you find them in Core Audio and similar frameworks of Cocoa. A callback is typically passed in as a parameter in some code that manages an event that might occur (and recur) over time.
At some point (the end of a movie clip, for example), the process is over, and certain completion code needs to be called. This is a common example of a callback. You pass in a pointer to the completion routine, and it is called at the appropriate moment. Callbacks used in this way are often a more elegant and efficient way than polling the ongoing event to see if it is finished and then performing the completion routine.
GO TO Hour 17, “Extending a Class with Protocols and Delegates,” p. 231, for information on delegates, which in some cases can provide similar functionality to blocks.
One way of looking at callback functions is that in some ways they are like supercharged functions. A function can take any number of arguments that you pass in when you call it (as long as they are defined). This goes to the heart of what a function is—code that can function on data that is passed into it at runtime.
Callback functions are functions that can be passed in at runtime, just as is the case with data (again, as long as the definition allows them). Being able to change not only the data on which a function acts, but also the functions that it may call in the course of its processing allows for a great deal of flexibility.
The only tricky part of callback programming is that you need to somehow package a C function in a format that can be passed in as a parameter to the function or method that is going to call it. You do that by passing it in as a function pointer. There are two versions of function pointers:
Function pointers using typedef
Inline function pointers
The two styles are shown in the following sections.
Tip
Instead of using a typedef
, you can use an inline function pointer to achieve the same result. This means a bit less typing (some people would say it is less clear for that reason).
Callback routines are part of C and many other languages. In C, they are implemented with basic constructs of the language that may be put together in a way that people are unfamiliar with, but there really is nothing new there.
The most important point to remember is that with both the inline function pointer and the typedef
declaration, the name of the function is a dummy. You replace it in your use with a function that you declare.
Blocks in Objective-C are implemented differently from callback functions in three important ways:
Objective-C blocks are objects at runtime.
Blocks can access data from the scope in which they are defined. This means that when a block is called, it has access to the data available where it was defined even if that data is no longer available. The access to this data is read-only.
The syntax of a block declaration uses a special character (^), and for many people this makes the block syntax more readable.
Note: Callbacks, Delegates, and Blocks
Blocks have similarities to callback functions. Also, as noted previously in Objective-C, the functionality of callback functions is often implemented using delegates. Bear in mind that these three concepts are related, but they are certainly not identical. In this section, you will see the basics of how to work with blocks. The main example is roughly comparable to the example shown for callbacks, but it is not identical.
Just as with callback functions, you can create a block as a variable or as a typedef
; you can also create a block inline. Although it is slightly more verbose, declaring a block as a variable or using a typedef
is somewhat clearer, and as you will see, in some cases, it is preferred.
As noted previously, this section is generally similar to the preceding function pointer section.
You can declare a block as a variable. The main difference in the syntax is that block declarations use a carat (^) rather than an asterisk, as is the case with pointers. You can declare a block using syntax such as this:
float(^myBlock)(int, float);
This represents a block named myBlock
that will take two arguments, an int
and a float
. It will return a float
.
The typedef
method shown for function pointers is preferred if you are going to be using this pattern several times. It can put your block declarations together in one place, and the same definition can be used for several blocks. The previous declaration can become a typedef
as follows:
typedef float(^MyBlockType)(int, float);
To create a variable, use the typedef
just as you ordinarily would, and provide the code for the block:
MyBlockType myBlock = ^(int myInt, float myDivisor) {
return myInt / myDivisor;
};
Use a block variable just as you would a function:
float myFloat = myBlock (17, 2.0);
Note: Finding Out More About Blocks and Block Syntax
The code in the previous steps is deliberately basic and bare bones. Continue to the next section, “Exploring Blocks in Cocoa,” to see real-life examples of blocks and how they can be used. You also see how to use a block variable as a function argument in Cocoa methods.
Some of the major enumeration methods are described in this section, but you should be aware that they are far from the only enumeration methods in Cocoa that use blocks. Also, these are just the highlights of the methods for some of the classes that implement block-based enumeration. Check the class reference for the classes you are interested in to find more enumeration methods.
All of these methods use the same basic structure: They enumerate over an object that may be a collection object or an object that otherwise consists of discrete entities such as lines in a string. They specify the format of the block that you pass in (that is, its signature). One of the arguments in the block is typically a BOOL
named stop. You set this to YES
to stop the enumeration at that point.
Here is an overview of the major block enumeration methods in Cocoa. Each has a companion method that includes an options
argument. Thus, NSArray
has enumerateObjectsUsingBlock:
as well as enumerateObjectsWithOptions:usingBlock:
.
The options are as follows:
NSEnumerationConcurrent—This is taken as a hint but might improve the speed on multicore devices. Your block’s code must be safe against concurrent invocation (that is, it must be able to be performed on two cores at the same time without each copy interfering with the other).
NSEnumerationReverse—This is undefined for NSDictionary
and NSSet
objects because they are unordered. When selected together with NSEnumerationConcurrent
, it is also undefined. It is a good idea to stay away from undefined operations. They are not errors, but their behavior is indeterminate.
Tip: You Can Enumerate Small Objects
When you think about enumerating over items, it is reasonable to think about the computer whirring away through collections with hundreds or even thousands of objects in them. That is a reasonable thought, and, particularly with today’s multicore computers running Cocoa-based apps, the speed is impressive.
However, small-scale enumerations are just as valuable, and an argument can be made in favor of using block enumerations over only a single object to be enumerated. The rationale for using small-scale enumerations is that the blocks structure your code and focus your attention very well. Among other things, this means that if at some time in the future you need to do the same thing to 1,000 objects that you do to a single object in a special-case part of your app, the conversion to a block-based enumeration is simple.
This is a frequently used method that enumerates each line in a string and applies a block to it. Note that in this and other interfaces, the actual block code is shown as {...}. Here is the interface:
– (void)enumerateLinesUsingBlock:(void (^)(NSString *line, BOOL *stop)){...}
To help you construct this and the following method calls, here is a typical basic implementation. You can use a block that you have declared as described in the previous section, “Using a Block Variable.” However, just as with function pointers, sometimes an inline declaration instead of a variable is easier and faster to write. It may be less clear to future code maintainers, and if you are using it in several places, the block declaration is preferable.
[myString enumerateLinesUsingBlock: ^(NSString *line, BOOL *stop) {
// extract value from line using something like
// if value is END...
*stop = YES; }
];
As each line
is enumerated, your block is called with that line stripped of its terminators so that you can quickly deal with it. The two typical things that you might want to do are the following:
Evaluate the line and possibly store its data in an object or other location.
Based on your evaluation, you may stop the enumeration.
This method starts from the beginning of the array and continues with each item until it reaches the end or until you set the block variable stop
to YES
.
Note that you get each object in turn back in obj
; you also get its index in idx
. Based on either value (or something else entirely), you can stop the enumeration.
– (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL
*stop)){...}
Listing 20.1 shows an example of how you would use that method on an array called myArray
:
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
do something with obj and/or idx
if (someTest)
{stop = YES};
}];
Sets are not ordered, so if you compare the NSSet
method with the NSArray
method, you will see that the index is not returned or set:
– (void)enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop)) {...}
Dictionaries are not ordered, but for each value there is a key. Thus, the basic enumeration method for NSDictionary
looks a bit like the method for NSArray
, returning a value and a key for NSDictionary
compared with a value and an index for NSArray
:
– (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))
{...}
Blocks are objects that, by default, are allocated on the stack. When execution leaves the scope in which the block is defined, it means they are gone—unless you copy them to the heap.
To copy a block to the heap, declare it as a block variable and then copy with this code:
[myBlock copy];
There are also C functions: Block_copy()
and Block_release()
.
You can use the __block storage
type modifier for local variables as in this case:
__block double myDouble;
This places the variable in a storage location that is shared between the normal scope of the variable and all blocks declared or created within that scope. This means that you can import this variable into any of those blocks and modify it if you need to.
This hour explored blocks and how they are different from C callback functions. You have seen how to create them as the syntax to use them with basic Cocoa objects, such as the enumeration methods for NSArray
, NSDictionary
, and NSet
.
Q. What variables can a block reference?
A. A block can reference variables declared as __block
in the scope in which the block is declared as well as the arguments passed into it.
Q. What is the preferred way to declare a block?
A. Using a typedef
is the preferred way to declare a block. If you are declaring it inline, that can be more concise.
1. How long can a block be?
2. If you are using a __block
variable in a block, what happens when control exits the scope in which the variable is declared?
1. There is no hard-and-fast rule, but most blocks are short and to the point.
2. It is still valid in the block until the block is exited.
Experiment with the enumeration methods described in this chapter, adding NSLog
methods to see what is happening as you enumerate and evaluate items in the collections.
18.221.136.142