Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

Blocks

Blocks are similar in many ways to functions, with the important exception that blocks have direct access to variables in their surrounding context. "Direct access" means that a block can use variables declared in its surrounding context even though those variables are not passed into the block through an argument list. Blocks are declared as follows:

^(argument_list){ body };

Blocks begin with a caret (^), followed by an argument list enclosed in parentheses and one or more statements enclosed in curly brackets. This expression is called a block literal, and is defined as follows:

  • Blocks can return a value by using a return statement in the body.
  • You don't have to specify the return type; the compiler determines the return type by looking at the body of the block.
  • If the block doesn't have arguments, the argument list is written as (void).
  • Block literals don't have a name; that is why they are sometimes called anonymous functions.

The following is a simple block example that takes an integer, doubles it, and returns the doubled value:

^(int n){ return n*2; };

You can call a block literal directly by appending values for its arguments surrounded in parentheses:

int j = ^(int n){ return n*2; }( 9 ); // j is now 18

This works, but it is a bit silly; you could get the same result by simply coding the following:

int j = 2 * 9;

In normal use, a block literal is assigned to a variable typed as pointer to a block, or used as an argument to a function or a method that takes a pointer to block argument.

Block Pointers

A variable that is a block pointer (a pointer to a block) is declared as follows:

   return_type (^name)(list of argument types);

This should look familiar; it has exactly the same form as the declaration of a function pointer, except that the * has been replaced with a ^.

The following example illustrates how to declare a block pointer, assign a block to it, and then call the block through the block pointer:

int (^doubler)(int);  // doubler is typed as a pointer to a block

doubler = ^(int n){ return n*2; };

int j = doubler( 9 );  // j is now 18

You can use block pointers as arguments to a function or a method:

// someFunction is a function that takes a block pointer as an argument

void someFunction( int (^blockArg)(int) );
int (^doubler)(int)= ^(int n){ return n*2; };

someFunction( doubler );

You can also use a block literal directly in a function call or an Objective-C message expression:

void someFunction( int (^blockArg)(int) );

someFunction( ^(int n){ return n*2; } );

Access to Variables

A block has:

  • Read-only access to automatic variables visible in its enclosing scope1
  • Read/write access to static variables declared in a function and to external variables
  • Read/write access to special variables declared as block variables

Here is a simple example of a block accessing a local variable in its enclosing scope:

int j = 10;

int (^blockPtr)(int) = ^(int n){ return j+n; };

int k = blockPtr( 5 );  // k is now 15

The value that a block uses for a local variable from its context is the value that local variable has when the flow of execution passes over the block literal, as shown here:

1 int j = 10;
2
3 int (^blockPtr)(int) = ^(int n){ return j+n; };
4
5 j = 20;
6
7 int k = blockPtr ( 5 );  // k is 15, not 25 as you might expect

In the preceding code:

  • Line 1: The local variable j is set to 10.
  • Line 3: The block blockPtr is defined. blockPtr has access to the local variable j. The value of j that blockPtr uses is bound to the value that j has when the program execution passes over this line. In effect, blockPtr now has a private copy of j that is set to 10.
  • Line 5: To show that blockPtr's value of j was bound in Line 3, j is reset to 20.
  • Line 7: The blockPtr is evaluated with an argument of 5 resulting in a return value of 10 (the value of j at the time Line 3 is executed) + 5 (the argument) = 15.

A block's access to local variables is read-only. The following code, which attempts to set the value of j from inside blockPtr, results in a compiler error because blockPtr's access to the local variable j is read-only:

int j = 10;

void (^blockPtr)(void) = ^(void){ j = 20; };

The compiler gives blocks access to static and external variables by pointer. The value the block sees for this type of variable is the value the variable has when the block is executed, not when the block is defined:

static int j = 10;

int (^blockPtr)(int) = ^(int n){ return j+n; };

j = 20;

int k = blockPtr ( 5 ); // k is 25

Block Variables

Variables declared with the new type modifier __block are called block variables:

__block int integerBlockVariable;

Block variables are visible to, and are shared and mutable by, any block defined in their scope. For example:

1 __block int j = 10;
2
3 void (^blockPtr_1)(void) = ^(void){ j += 15; };
4 void (^blockPtr_2)(void) = ^(void){ j += 25; };
5
6 blockPtr_1(); // j is now 25
7 blockPtr_2(); // j is now 50

In the preceding code:

  • Line 1: j is declared as a __block variable and set to 10.
  • Line 3: blockPtr_1 is defined. Because j has been declared as a __block variable, blockPtr_1 is permitted to set the value of j.
  • Line 4: Similarly, blockPtr_2 is permitted to set the value of j. Both blockPtr_1 and blockPtr_2 share read-write access to the variable j.
  • Line 6: b lockPtr_1 is evaluated, incrementing j by 15, resulting in a value of 25 for j.
  • Line 7: blockPtr_2 is evaluated, incrementing j by 25, resulting in a value of 50 for j.

Block variables start out on the stack like any other automatic variable. But if you copy a block that references a block variable, the block variable is moved from the stack to the heap along with the block (see the section Copying Blocks).

Blocks Are Stack Based

When you define a block literal inside a function or a method, the compiler creates a structure on the stack that holds the values of any local variables the block references, the addresses of read/write variables it references, and a pointer to block's executable code.

Blocks have the same lifetime as automatic variables. When the block literal goes out of scope, it is undefined, just like an automatic variable that has gone out of scope. Scope for a block literal is defined in the same way as scope for an automatic variable (see Chapter 2, "More About C Variables"): If a block literal is defined inside a compound statement (between a pair of curly brackets), the block literal goes out of scope when the program execution leaves the compound statement. If a block literal is defined in a function, it goes out of scope when the function returns. The following code is incorrect:

int (^doubler)(int);
{
   ...
   doubler = ^(int n){ return n*2; };
   ...
}
...
int j = doubler( 9 );  // WRONG! Bug!

In the preceding example, j is undefined. At the point where doubler(9) is executed, the block that the doubler variable points to has gone out of scope and the block may have been destroyed.

Global Blocks

You can also assign a block literal to a file-scope block pointer variable:

#import <Foundation/Foundation.h>

void (^logObject)(id) =
  ^(id obj){ NSLog( @"Object Description: %@", [obj description]); };

// logObject( someObj ) may be used anywhere in the file.

The compiler creates global-scope blocks in low memory like any other file-scope variable. Global blocks never go out of scope.

Blocks Are Objective-C Objects

It may seem surprising, but blocks are also Objective-C objects. A newly created block is the only example of an Objective-C object that is created on the stack. Blocks are instances of one of several private subclasses of NSObject. Apple doesn't provide the header for the block classes so you can't subclass them or do much of anything with them in an Objective-C sense except send them copy, retain, release, and autorelease messages. Copying and memory management for blocks are covered in the next sections.

Copying Blocks

One of the main uses of blocks is to pass a chunk of work (some code plus some context) out of the current scope for processing at a later time. Passing a block to a function or method that you call is safe (as long as that function or method is going to execute on the same thread). But what happens if you want to pass a block to a different thread or pass a block out of the current scope as a return value? When the current function or method returns, its stack frame is destroyed. Any blocks that were defined in its scope become invalid.

To preserve a block, you must copy it. When you copy a block, the copy is created on the heap. The heap-based copy is then safe to return up the stack to the calling function or pass off to another thread.

If you are coding in straight C, you can use the Block_copy() function, as follows:

int(^doublerCopy)(int) = Block_copy( ^(int n){ return n * 2; } );

In Objective-C, you can send a copy message to the block:

int(^doublerCopy)(int) = [^(int n){ return n * 2; } copy];

The two preceding examples are equivalent. In either statement, you could use a block pointer instead of the block literal.

When you copy a block, the new block gets copies of the values of any automatic variables that the block references. (The block accesses automatic variables by value. The value of the variable is copied into the block object when it is created.)

Memory Management for Blocks

If you copy a block with Block_copy(), you must eventually balance that call with a call to Block_release(). If you use the Objective-C copy message and you are using reference counting, you must balance the copy message with a release or an autorelease:

int(^getDoublerBlock())(int)
{
  int(^db)(int) = ^(int n){ return 2*n; };

  // The returned block is autoreleased. This balances the copy
  // and makes getDoublerBlock conform to the naming convention
  // for memory management.
  return [[db copy] autorelease];
}
...

int(^doubler )(int) = getDoublerBlock();  // Get the block

int sevenDoubled = doubler(7); // Use the block

Don't mix calls to Block_copy() and Block_release() with the Objective-C's copy and release messages.

If a block references a variable that holds an object, that object is retained when the block is copied and released when the block is released.

When copying a block inside a method body, the rules are slightly more complicated:

  • A direct reference to self in a block that is being copied causes self to be retained.
  • A reference to an object's instance variable (either directly or through an accessor method) in a block that is being copied causes self to be retained.
  • A reference to an object held in a local variable in a method causes that object, but not self, to be retained.

You should be careful when copying a block. If the code that copies the block is inside a method and the block refers to any of the object's instance variables, the copy causes self to be retained. It is easy to set up a retain cycle that prevents the object from ever being deallocated,

Listing 16.3 shows the interface section for a class that has an instance variable name to store a name, and a method logMyName to log that name. logMyName uses a block stored in the instance variable loggingBlock to do the actual logging.

Example 16.3. ObjectWithName.h

#import <Foundation/Foundation.h>

@interface ObjectWithName : NSObject
{
  NSString *name;
  void (^loggingBlock)(void);
}

- (void) logMyName;
- (id) initWithName:(NSString*) inName;

@end

Listing 16.4 shows the corresponding implementation file.

Example 16.4. ObjectWithName.m

 1 #import "ObjectWithName.h"
 2
 3 @implementation ObjectWithName
 4
 5 - (id) initWithName:(NSString*) inputName
 6 {
 7   if (self = [super init] )
 8     {
 9       name = [inputName copy];
10       loggingBlock = [^(void){ NSLog( @"%@", name ); } copy];
11     }
12   return self;
13 }
14
15 - (void) logMyName
16 {
17   loggingBlock();
18 }
19
20 - (void) dealloc
21 {
22   [loggingBlock release];
23   [name release];
24   [super dealloc];
25 }

ObjectWithName is a very simple class. However, this version of ObjectWithName has a retain cycle. If you create an ObjectWithName object, it won't be deallocated when you release it.

The problem is Line 10 of Listing 16.4:

loggingBlock = [^(void){ NSLog( @"%@", name ); } copy];

To store the block in the instance variable loggingBlock, you must copy the block literal and assign the copy to the instance variable. This is because the block literal goes out of scope when initWithName: returns. Copying the block puts the copy on the heap (like a normal Objective-C object). However, the block literal references the instance variable name, so the copy causes self to be retained, setting up a retain cycle. The block now has ownership of the object and the object has ownership of the block (because it has copied the block). The object's reference count never goes to zero and its dealloc method is never called.

You can fix this problem by changing Line 10 of Listing 16.4 so it reads as follows:

loggingBlock = [^(void){ NSLog( @"%@", inputName ); } copy];

With this change, the block copying operation retains the input argument inputName rather than the instance variable name. Because the block no longer references any of the object's instance variables, self is not retained and there is no retain cycle. The object will still have the same behavior because name and inputName have the same content.

Traps

Because blocks are stack-based objects, they present some traps for the unwary programmer. The following snippet of code is incorrect:

void(^loggingBlock)(void);

BOOL canWeDoIt = ...

// WRONG

if ( canWeDoIt )
  loggingBlock = ^(void){ NSLog( @"YES" ); };
else
  loggingBlock = ^(void){ NSLog( @"NO" ); };

// Possible crash
loggingBlock();

At the end of this snippet, loggingBlock is undefined. The if and else clauses of an if statement and the bodies of loops are separate lexical scopes, even if they are single statements and not compound statements. When the program execution leaves the scope, the compiler is free to destroy the block and leave loggingBlock pointing at garbage.

To fix this code, you must copy the block, and then remember to release it when you are finished:

void(^loggingBlock)(void);

BOOL canWeDoIt = ...

if ( canWeDoIt )
  loggingBlock = [^(void){ NSLog( @"YES" ); } copy];
else
  loggingBlock = [^(void){ NSLog( @"NO" ); } copy];

// Remember to release loggingBlock when you are finished

This example is also incorrect:

NSMutableArray *array = ...

// WRONG!

[array addObject: ^(void){ doSomething; }];
return array; //

Recall that objects added to collection objects receive a retain message; however in this case, the retain doesn't help because retain is a no-op for a stack-based block. Again, to fix the problem, you must copy the block:

NSMutableArray *array = ...
[array addObject: [[^(void){ doSomething; } copy] autorelease]];
return array;

In the preceding code snippet, the copy message puts a copy of the block on the heap. The autorelease message balances the copy. The retain message that the copied block receives when it is placed in the array is balanced by a release message when the block is later removed from the array or when the array is deallocated.

Blocks in Cocoa

Beginning with Mac OS X Snow Leopard (v 10.6), Apple has started deploying blocks throughout the Cocoa frameworks. This section briefly describes three areas where Apple has added features that use blocks.

Concurrency with NSOperationQueue

Concurrent (multithreaded) programming is very difficult to do correctly. To make it easier for programmers to write error-free multithreaded programs, Apple has introduced Grand Central Dispatch (GCD). GCD implements concurrency by creating and managing a thread pool. A thread pool is a group of threads that can be assigned to various tasks and reused when the task is finished. GCD hides the details of managing the thread pool and presents a relatively simple interface to programmers.

The Cocoa class NSOperationQueue provides a high-level interface to GCD. The idea is simple: You create an NSOperationQueue and add units of work, in the form of blocks, for the queue to execute. Underneath NSOperationQueue, GCD arranges to execute the block on a separate thread:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[queue addOperationWithBlock: ^(void){ doSomething; } ];

// doSomething will now execute on a separate thread
  • A block passed to GCD (either through NSOperationQueue or through the low-level C interface) must have the form:

    void (^block)(void)
    

    It must not take arguments or return a value.

  • The GCD mechanism takes care of copying blocks submitted to it and releases them when no longer needed.

Collection Classes

The Foundation collection classes now have methods that enable you to apply a block to every object in the collection. NSArray has the following method:

- (void)enumerateObjectsUsingBlock:
       (void (^)(id obj, NSUInteger idx, BOOL *stop))block

This method calls block once for each object in the array; the arguments to block are:

  • obj, a pointer to the current object.
  • idx, the index of the current object (idx is the equivalent of the loop index in an ordinary for loop).
  • stop, a pointer to a BOOL. If the block sets stop to YES, -enumerateObjectsUsingBlock: terminates when the block returns. It is the equivalent of a break statement in an ordinary C loop.

Listing 16.5 uses -enumerateObjectsUsingBlock: to log a description of every object in an array.

Example 16.5. DescribeArrayContents.m

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  NSArray *array =
      [NSArray arrayWithObjects: @"dagger", @"candlestick",
           @"wrench", @"rope", nil];

  void (^loggingBlock)(id obj, NSUInteger idx, BOOL *stop) =
      ^(id obj, NSUInteger idx, BOOL *stop)
          { NSLog( @"Object number %d is a %@",
               idx, [obj description] ); };

  [array enumerateObjectsUsingBlock: loggingBlock];

  [pool drain];
  return 0;
}

If you build and run this program, you should see the following result:

DescribeArrayContents [50642:a0b] Object number 0 is a dagger
DescribeArrayContents [50642:a0b] Object number 1 is a candlestick
DescribeArrayContents [50642:a0b] Object number 2 is a wrench
DescribeArrayContents [50642:a0b] Object number 3 is a rope

Did-End Callbacks

I haven't said much about AppKit in this book, but I'll assume that you are familiar with saving files on Mac OS X. You select File > Save in an app, and if this is the first time the file is saved, a save sheet appears so you can name the file and select the location where it will be saved. You make your choices and click Save, or if you've changed your mind, you can click Cancel. After clicking one of the buttons, the sheet slides up and disappears.

When you invoke the method that begins the sheet, Cocoa gives you the chance to register some code to be executed when the user dismisses the sheet. (This is where you put the code that actually saved the file to disk.)

Prior to Mac OS X Snow Leopard (v 10.6), a Save sheet was started with this rather formidable method:

- (void)beginSheetForDirectory:(NSString *)path
         file:(NSString *)name
         modalForWindow:(NSWindow *)docWindow
         modalDelegate:(id)modalDelegate
         didEndSelector:(SEL)didEndSelector
         contextInfo:(void *)contextInfo

When the user dismisses the sheet, the sheet sends the object registered as modalDelegate, the message represented by the selector didEndSelector. Typically, the modalDelegate is the object that initiates the panel. didEndSelector has the form:

- (void)savePanelDidEnd:(NSSavePanel *)sheet
             returnCode:(int)returnCode
            contextInfo:(void *)contextInfo;
  • sheet is a pointer to the NSSavePanel object itself.
  • returnCode is an integer that specifies which button the user clicked on.
  • contextInfo is a blind pointer to the information passed to beginSheetForDirectory: ... when it was invoked. This is how you pass information from the object that invoked the sheet to the code responsible for acting on the user's input.

For Mac OS X Snow Leopard and beyond, the preceding method has been deprecated3 and replaced with the following method:

- (void)beginSheetModalForWindow:(NSWindow *)window
               completionHandler:(void (^)(NSInteger result))handler

You simply pass in a block to be executed when the sheet is dismissed. The block can capture any required context so the blind contexInfo pointer is not required.

Style Issues

Placing the statements of a block literal on a single line makes debugging difficult; for example:

^(void){doStuff; doMoreStuff; evenMore; keepGoing; lastStatement;}

You can set a debugger breakpoint on doStuff; but there is no way to step through or set a breakpoint on any of the other statements in the block. If you stop on doStuff; and try to step, the debugger jumps to the line following the block literal—making it impossible to debug the subsequent lines in the literal. If your block literal is non-trivial and may require debugging, you should put the statements in the block's body on separate lines, as follows:

^(void){doStuff;
        doMoreStuff;
        evenMore;
        keepGoing;
        lastStatement;}

As noted earlier, you can place a block literal directly in a function or method call:

someFunction( otherArgs, ^(void){ doStuff;
                                  doMoreStuff;
                                  evenMore;
                                  keepGoing;
                                  lastStatement;} );

You could also assign the block to a block pointer variable and use the block pointer as the argument. Which you choose is a matter of preference: Some people are annoyed at creating an extra variable (which the compiler will probably optimize away), whereas others find that putting the block literal inside the function call makes the code hard to read.

  • + Share This
  • 🔖 Save To Your Account