Chapter     15

Blocks

Welcome back. Congratulations on making it to this point! So far, you have covered all the fundamental elements of the Objective-C platform. But guess what? You haven’t reached the finish line just yet! That’s because Objective-C has a variety of additional, advanced features that, once mastered, will enable you to take your programming to the next level. In Part 4 of this book, you will explore several of these features and learn how to use them in your programs.

In this chapter, you will learn how to program with blocks, a powerful feature available for the Objective-C language. The chapter explores block syntax and semantics, memory management, how to develop blocks in your own code, and how to use blocks in existing APIs (such as the Foundation Framework).

Simply put, blocks provide a way to create a group of statements (i.e., a block of code) and assign those statements to a variable, which can subsequently be invoked. In this way, they are similar to functions and methods, but in addition to executable code, they may also contain variable bindings to stack or heap memory. A block is an implementation of a closure , a function that allows access to variables outside its typical scope. In addition, an Objective-C block is actually an object; it is a subclass of NSObject and has all of its associated properties (you can send messages to it, etc.). Apple developed blocks as an extension to the C family of programming languages (C, Objective-C, and C++). They are available for use with Mac OS X v10.6 and later, as well as iOS 4.0 and later.

Block Syntax

One of the most difficult things about learning how to program with blocks is mastering their syntax. This section explains block syntax in depth and provides numerous examples to illustrate their application in code.

The syntactic features added to support blocks enable the declaration of variables of block type and the definition of block literals.

The block type consists of a return value type and a list of parameter types. A caret symbol (^) operator is used to declare a variable of block type. Figure 15-1 illustrates the syntax for a block type declaration.

9781430250500_Fig15-01.jpg

Figure 15-1. Block type declaration syntax

Figure 15-1 shows that the returnType is the type of the value returned by the block when it is invoked. The block variable named blockName is preceded by a caret and enclosed in parentheses. As mentioned previously, a variable of block type may have parameters. The list of parameter types is enclosed in parentheses and follows the block name, where the parameterTypes are each separated by a comma. If a block variable declaration has no parameters, its list of parameter types should be specified as void (enclosed in parentheses). OK, that summarizes the syntax for declaring variables of block type. Now let’s look at some examples. The following statement declares a block variable named oneParamBlock that has a single parameter of type int and returns a value of type int.

int (^oneParamBlock)(int);

As stated, a block variable with no return value has its return type specified as void in its declaration. The next example declares a block variable named twoParamsBlock that takes two parameters of type int and returns nothing.

void (^twoParamsBlock)(int, int);

A block declaration can also include parameter names, similar to an Objective-C method declaration. The following shows the previous example with named input parameters.

void (^twoParamsBlock)(int parm1, int parm2);

The next example declares a block variable named noParamsBlock with no parameters and that returns a value of type int.

int (^noParamsBlock)(void);

Because variables can be declared with block type, a block variable can serve as a function argument or method parameter. In these cases, it is common to create a type definition (via a typedef statement) to provide an alternate name for a block type, thereby simplifying its subsequent usage in your code. Listing 15-1 demonstrates the declaration of a method that takes a block as a method parameter.

Listing 15-1.  Block Variable in a Method Declaration

typedef int (^AdderBlock)(int);
@interface Calculator : NSObject
- (int)process:(int)count withBlock:(AdderBlock)adder;
@end

Listing 15-1 shows that a typedef statement is used to create a block type named AdderBlock. This type is subsequently used as the parameter type in a method declaration.

A block literal expression defines a reference to a block. It begins with the caret operator, followed by a return type, the list of parameters, and then a collection of statements (the block statement body) enclosed in braces. The syntax for a block literal expression is shown in Figure 15-2.

9781430250500_Fig15-02.jpg

Figure 15-2. Block literal expression syntax

As you see in Figure 15-2, the caret symbol at the beginning of the expression indicates the beginning of a block literal expression, with the optional returnType specifying the type of the return value of the statement body. The block parameters (passed into the body of the block) are enclosed in parentheses with a type and a name for each, separated by commas. The block code (enclosed within braces) comprises the statements executed when the block is invoked. If the block definition specifies a return type, the code includes a return statement with a value of the specified returnType. If there are multiple return statements in a block literal expression, the return type specified must be identical for each. Now let’s examine a few simple block literal expressions. Listing 15-2 takes a single parameter of type int and returns a value that equals its input parameter incremented by 1.

Listing 15-2.  Block Literal Expression

^int (int addend)
{
  return addend + 1;
}

As mentioned previously, it is not required to specify the return type for a block definition, because the compiler will infer it from the return statement within the body of the block. Listing 15-3 modifies Listing 15-2 by not specifying the return type in the expression.

Listing 15-3.  Block Literal Expression, No Return Type

^(int addend)
{
  return addend + 1;
}

If a block literal expression has no parameters, then the parenthesized parameter list is not required. Listing 15-4 provides an example of a block literal with no parameters and no return value, and that logs a greeting to the console.

Listing 15-4.  Block Literal Definition, No Parameters

^{
  NSLog(@"Hello, World!");
}

A block literal can be assigned to a corresponding block variable declaration. Listing 15-5 demonstrates the declaration and subsequent definition of a block variable named oneParamBlock.

Listing 15-5.  Block Declaration and Definition

int (^oneParamBlock)(int);
oneParamBlock = ^(int param1)
{
  return param1 * param1;
};

As you would expect, the block declaration and definition can be combined into one statement. Listing 15-6 demonstrates the compound declaration and definition of a block variable named incBlock.

Listing 15-6.  Compound Block Declaration and Definition

int (^incBlock) (int) = ^(int addend)
{
  return addend + 1;
};

As shown in Listing 15-6, the block literal expression parameter type(s) and return type must conform to the corresponding block variable declaration.

Comparing Figures 15-1 and 15-2, you will note that there are several differences between the syntax of a block variable declaration and a block literal expression. For example, a block variable parameter types list is not optional; if there are no parameters, then a single void parameter type must be provided. Contrast that with a block literal expression for which the parameter list is optional—if there are no parameters, then the list may be omitted. Table 15-1 summarizes key differences in the syntax of block variable declarations and block literal expressions.

Table 15-1. Comparison of Block Syntactic Elements

Block Syntactic Element Block Variable Declaration Block Literal Expression
Caret operator Begins a block variable declaration. The caret precedes the variable name, both of which are enclosed in parenthesis. Begins a block literal expression.
Name The block variable name is required. Block literal expressions are nameless.
Return type The return type is required in a block variable declaration. A block variable with no return value has a declared return type of void. The return type is optional in a block literal expression because the compiler will infer it from the type of the return value in the expression statement body. If the expression statement body has more than one return statement, they all must be of the same type.
Parameters The parameter type list is mandatory in a block variable declaration. If the block variable has no parameters, then the declared parameter type list is void. A parameter list is not required in a block literal expression that has no parameters.

Once a variable of block type has been declared and assigned to a conforming block literal expression, it can be invoked directly with the invoke operator. This syntax is similar to a C function call syntax, with a list of parameters corresponding to the block declaration. (The type of the return value, if any, also corresponds to the block type declaration.) The following statement invokes the block in Listing 15-6 and assigns the result to the variable myValue.

int myValue = incBlock(5);

A block expression can also be defined and invoked in a single statement. Recall that a block expression is anonymous; hence, the invoke operator takes an anonymous (i.e., nameless) function followed by a parameter list that corresponds to the block expression. Listing 15-7 defines a block expression and invokes it in a single statement that logs the message “Greetings, Earthling!” to the console.

Listing 15-7.  Block Expression Definition and Invocation

^(NSString *user)
{
  NSLog(@"Greetings, %@!", user);
}(@"Earthling");

A block expression can also be defined inline as an anonymous function, thereby serving as a parameter of a function or method invocation. The recommended practice in these scenarios is to use only one block literal as a parameter to a function or method, and that it be the last parameter in the list (if the function/method has other parameters). Listing 15-8 invokes an instance method (from the class interface shown in Listing 15-1) that creates a block literal expression and uses it as a parameter to a method invocation.

Listing 15-8.  Block Literal Expression in a Method Invocation

Calculator calc = [Calculator new];
int value = [calc process:2
                withBlock:^(int addend)
                {
                  return addend + 1;
                }];

Many Objective-C APIs, including the Foundation Framework, contain methods that take a block as a parameter. Later in this chapter, you will develop several programs with blocks, so take your time and review this material as long as necessary until you thoroughly grasp the syntax.

Blocks Are Closures!

As stated earlier in this chapter, a block is an implementation of a closure, a function that allows access to local variables declared outside of its typical scope. To understand what this means, let’s take a moment to understand scope and visibility rules. The visibility of a variable refers to the portion(s) of a program in which it can be accessed; this is also referred to as a variable’s scope. For example, variables declared within a C function definition have local scope, meaning that they are visible and accessible within the function, and not accessible elsewhere. (Note that a function can also reference global variables.) A block augments the visibility of variables compared to C functions through several features, as follows:

  • Support for lexical scope. This is the ability to reference local variables and parameters from an enclosing scope.
  • __block variables. The __block keyword, applied to a variable outside a block but within the same lexical scope, enables this variable to be modified within a block.
  • Instance variable access. A block defined within an object’s method implementation can access the object’s instance variables.
  • const imports. Constant variables imported through a header file (via an #import or #include directive) are visible within a block literal expression.

Lexical Scope

One of the unique features of blocks is its support for lexical scoping. In contrast, C functions do not have access to local variables declared outside of their definition. The example shown in Listing 15-9 will not compile because the local variable myVar is not visible within the logValue function definition.

Listing 15-9.  C Function Attempting to Access a Local Variable Outside Its Scope

void logValue()
{
  // ERROR, illegal access of myVar, not within scope
  NSLog(@"Variable value = %d", myVar);
}

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    int myVar = 10;
    logValue();
  }
  return 0;
}

A block supports lexical scoping, thus a block literal expression has access to variables declared within the same lexical scope. In addition, blocks can be defined anywhere other variables can; for example, within other blocks, functions, and methods. C functions, on the other hand, cannot be defined within other functions or methods. Listing 15-10 compiles and runs successfully because the variable myVar is declared within the same lexical scope as the block logValueBlock.

Listing 15-10.  Block Accessing Local Variable Through Lexical Scoping

{
  int myVar = 10;
  void (^logValueBlock)(void) = ^{
    NSLog(@"Variable value = %d", myVar);
  };
  logValueBlock();
}

Listing 15-10 shows that a block has access to local variables declared outside its definition. Specifically, these local variables are declared (and initialized) within an enclosing scope, prior to the block literal expression. Braces delimit a local scope; in addition scopes may be nested. Referring again to Listing 15-10, the variable myVar is declared at the same scope as the block definition assigned to logValueBlock, prior to the literal expression, and thus can be used within the expression. On the other hand, Listing 15-11 will not compile, because the local variable myVar is declared and initialized after the block literal expression.

Listing 15-11.  Illegal Access of Local Variable in Block, Declared After Block Literal Expression

void (^logValueBlock)(void) = ^{
  // ERROR, illegal access of myVar, declared after literal expression
  NSLog(@"Variable value = %d", myVar);
};
int myVar = 10;
logValueBlock();

Local variables accessed through lexical scoping behave as constant values (for primitive types) or by reference variables (for objects) within a block literal expression. Listing 15-12 will not compile because a primitive variable accessed through lexical scoping cannot be modified within a block literal.

Listing 15-12.  Block Attempting to Modify a Local Variable Accessed Through Lexical Scoping

int myVar = 10;
void (^logValueBlock)(void) = ^{
  // ERROR, lexical scope variable not assignable
  myVar = 5;
  NSLog(@"Variable value = %d", myVar);
};
logValueBlock();

As local scopes can be nested, a block can capture local variables within multiple (nested) scopes. This is demonstrated in the code from Listing 15-13.

Listing 15-13.  Block Capturing Local Variables Within Multiple Nested Scopes

for (int ii=0; ii<2; ii++)
{
  for (int jj=0; jj<3; jj++)
  {
    void (^logValueBlock)(void) = ^{
      NSLog(@"Variable values = %d, %d", ii, jj);
    };
    logValueBlock();
  }
}

Mutable __block Variables

By default, block local variables accessed through lexical scoping are visible but not writeable within a block literal expression. The __block storage type modifier can be used to make these variables read-write (i.e., mutable). The __block modifier can be used on all supported Objective-C types, except for C variable-length arrays (an array with a length that is not a constant expression) and C structures that contain variable-length arrays. Its use cannot be combined with the existing local storage modifiers auto, register, and static. Listing 15-14 illustrates the use of a __block variable.

Listing 15-14.  Correct Use of __block Storage

__block int myVar = 10;
void (^incBlock)(int) = ^(int amount){
  myVar += amount;
  NSLog(@"New value = %d", myVar);
};
incBlock(5);

Variables qualified with the __block storage type are copied to the heap if the referencing block is copied to heap storage. This last point brings up the topic of memory management with blocks, the subject of the next section.

Memory Management with Blocks

At runtime, a block literal expression is allocated on the stack and thus has the same lifetime as a local variable. As a result, it must be copied to permanent storage (i.e., onto the heap) to be used outside of the scope in which it is defined. For example, if you want to return a block literal from a method or save it for later use, then the block must be copied onto the heap and subsequently released when no longer in use. Listing 15-15 illustrates a block literal that’s used outside its scope. This may result in a dangling pointer that causes the program to crash.

Listing 15-15.  Block Literal Used Outside of Its Lexical Scope

void (^greetingBlock)(void);
{  // scope begins, local variables pushed onto stack
  greetingBlock = ^{
    NSLog(@"Hello, World!");
  };
}  // scope ends, stack variables (e.g. the block literal) popped off stack
greetingBlock();  // Block invocation may cause the program to crash!

Listing 15-15 shows that the block literal expression assigned to the variable greetingBlock has the same lifetime as a local variable; it thus no longer exists outside of its lexical scope. Objective-C includes copy (Block_copy()) and release (Block_release()) operations that provide memory management for block literals.

The Block_copy() operation copies a block literal onto the heap. It is implemented as a function that takes a block literal reference as its parameter and returns a block reference of the same type.

The Block_release() operation releases a block literal from the heap so that its memory is reclaimed. It is implemented as a function that takes a block reference as its parameter. A block reference is released from the heap if it matches a corresponding block reference that was copied to the heap; otherwise, the function call has no effect. In order to avoid a memory leak, a Block_copy() call must be balanced by a corresponding Block_release(). The Foundation Framework defines copy and release methods for blocks that are the functional equivalent of the Block_copy() and Block_release() functions. Listing 15-16 updates the code in Listing 15-15 with the appropriate block copy operations.

Listing 15-16.  Using the Block Copy and Release Methods

void (^greetingBlock)(void);
{
  greetingBlock = [^{
    NSLog(@"Hello, World!");
  } copy];
}
greetingBlock();          // Block invocation works (uses heap storage)
[greetingBlock release];  // released block to prevent memory leak

With ARC memory management, the compiler automatically performs the block copy and release operations as long as the block does not return an id type or pass an id type as a parameter. In either case, the copy and release operations must be performed manually, as with MRR memory management. Listing 15-17 demonstrates the use of these operations for a block that passes an id type as a parameter.

Listing 15-17.  Using the Block_copy and Block_release Functions for Blocks with an id Type Parameter

void (^greetingBlock)(id salutation);
{
  greetingBlock = Block_copy(^(id salutation){
  NSLog(@"%@, World!", salutation);
  });
}
greetingBlock(@"Hello");
Block_release(greetingBlock);

Variables declared with the __block storage type have different semantics, depending on whether or not MRR or ARC memory management is in use. Under MRR, __block variables are not retained if used within a block literal; however, under ARC, __block variables are retained if used within a block literal. What this means is that if you are using ARC memory management and don’t want a __block variable to be retained (for example, to avoid circular references), the __weak storage type should also be applied to the variable.

Using Blocks

Now that you understand the syntax for coding blocks and some of the key concerns with respect to their semantics and memory management, you can begin exploring how to best use blocks in your code. Apple developed blocks for use with Grand Central Dispatch (GCD) to support concurrent programming, and these APIs use blocks to schedule code for execution (GCD is covered in more detail in Chapter 17). However, there are many other ways to use blocks, both within existing APIs and in your own classes. In general, blocks are most naturally used to implement small, self-contained pieces of code that encapsulate units of work. They are typically executed concurrently (for example, via GCD) over the items of a collection or as a callback when an operation has finished. Because blocks can be defined inline, they can eliminate the need to create entirely separate classes and methods for context-sensitive code, such as asynchronous completion handlers. They also enable related code to be kept together, and not scattered among multiple files. As noted before, many Apple APIs (and an increasing number of third-party APIs) now use blocks. The next few sections will demonstrate these common usage scenarios as you implement several programs that use blocks.

Sorting an Array Using Blocks

Now you will create a program that demonstrates array sorting using blocks. In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify BlockArraySorter for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

In the Xcode project navigator, select the main.m file and update the file, as shown in Listing 15-18.

Listing 15-18.  BlockArraySorter main.m File

#import <Foundation/Foundation.h>
#include <stdlib.h>

#define ArrayElements 10

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Create an array of numbers with random values (0-99)
    NSMutableArray *numbers = [NSMutableArray arrayWithCapacity:ArrayElements];
    for (int elem=0; elem<ArrayElements; elem++)
    {
      unsigned int value = arc4random() % 100;
      [numbers addObject:[NSNumber numberWithUnsignedInt:value]];
    }
    NSLog(@"Values: %@", numbers);      // Log the numbers unsorted
    
    // Sort the array of numbers in ascending order
    [numbers sortUsingComparator:^(id obj1, id obj2){
      if ([obj1 integerValue] > [obj2 integerValue])
      {
        return (NSComparisonResult)NSOrderedDescending;
      }
      if ([obj1 integerValue] < [obj2 integerValue])
      {
        return (NSComparisonResult)NSOrderedAscending;
      }
      return (NSComparisonResult)NSOrderedSame;
    }];
    NSLog(@"Values: %@", numbers);      // Log the numbers sorted
  }
  return 0;
}

First, the file imports the stdlib.h header file via an #include preprocessor directive. This header file is required for the arc4random() function used later in the code. Next, the file defines a constant value, ArrayElements, which is used to control the number of elements in an array.

The main() function contains the body of the program logic. It first creates an NSMutableArray object and then adds 10 elements to the array; each element is an NSNumber object with a random number. The random number, obtained using the arc4random() function, is constrained to a value between 0–99 (inclusive) with the following statement.

unsigned int value = arc4random() % 100;

The values of the array are then logged to the output pane. Because the numbers were randomly selected and the array has not been sorted, the order of the values displayed in the output pane is random. Next, the array is sorted using the NSMutableArray sortUsingComparator: method. This method sorts the array using the comparison method specified by the block literal input parameter. The full method signature is

- (void)sortUsingComparator:(NSComparator)cmptr

Thus the block literal expression is of block type NSComparator, a Foundation Framework data type whose type definition is

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);

So an NSComparator is a block type that takes two parameters of type id and returns a value of type NSComparisonResult. NSComparisonResult is a Foundation Framework enumeration used to indicate the ordering of items in a request, its values are as follows:

  • NSOrderedAscending (the left operand is smaller than the right)
  • NSOrderedSame (the left and right operands are equal)
  • NSOrderedDescending (the left operand is larger than the right)

The block literal performs a comparison between array element values and returns the appropriate NSComparisonResult to sort the array elements based on ascending (numerical) order, as shown in Listing 15-18.

[numbers sortUsingComparator:^(id obj1, id obj2){
  if ([obj1 integerValue] > [obj2 integerValue])
  {
    return (NSComparisonResult)NSOrderedDescending;
  }
  if ([obj1 integerValue] < [obj2 integerValue])
  {
    return (NSComparisonResult)NSOrderedAscending;
  }
  return (NSComparisonResult)NSOrderedSame;
}];

Now save, compile, and run the BlockArraySorter program and observe the messages in the output pane (as shown in Figure 15-3).

9781430250500_Fig15-03.jpg

Figure 15-3. Testing the BlockArraySorter project

The messages in the output pane show that the array was correctly sorted using the block literal expression. It first displays the values for the unsorted array elements and then redisplays the array elements, this time sorted based on the numeric value of each element. Many of the Foundation Framework collection classes (NSArray, NSDictionary) contain methods that use a block literal to sort their elements, so this demonstrates a common use of blocks that you will encounter. Perfect. Now that you have one example under your belt, let’s develop another program that uses a block to perform an asynchronous callback function.

Loading a URL Using Blocks

Next, you will create a program that uses a block to asynchronously load a URL request, executing the block when the request has finished loading. In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify BlockURLLoader for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

In the Xcode project navigator, select the main.m file and update the file, as shown in Listing 15-19.

Listing 15-19.  BlockURLLoader main.m File

#import <Foundation/Foundation.h>

#define IndexURL       @"http://www.wikipedia.com/index.html"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Retrieve the current run loop for the connection
    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    BOOL __block downloadComplete = NO;

    // Create the request
    NSURLRequest *request = [NSURLRequest
                             requestWithURL:[NSURL URLWithString:IndexURL]];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue currentQueue]
                           completionHandler:^(NSURLResponse *response,
                                               NSData *data, NSError *error)
    {
      if (data == nil)
      {
        NSLog(@"Error loading request %@", [error localizedDescription]);
      }
      else
      {
        NSLog(@" Downloaded %lu bytes from request %@",
              [data length], [request URL]);
      }
      downloadComplete = YES;
    }];
    
    // Loop until finished downloading the resource
    while ( !downloadComplete &&
           [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
  }
  return 0;
}

After the Foundation Framework import, the file defines a constant value, IndexURL, for the URL to be downloaded.

The main() function contains the body of the program logic. It begins by retrieving the current run loop, required for asynchronous URL loading with NSURLConnection objects. It also declares and initializes a BOOL variable named downloadComplete. This variable is declared with __block storage type, thereby enabling its value to be changed within a block literal of the same scope. Next, an NSURLRequest instance is created (its URL is specified by the #define constant) using the convenience constructor requestWithURL:. Then the code creates an NSURLConnection object to load the request. It uses the sendAsynchronousRequest:queue:completionHandler: method. The completion handler is the handler block literal that is executed. The queue is an NSOperationQueue instance to which the handler block is dispatched when the request completes or fails. It is used to enable the execution of asynchronous operations. The block literal checks the return value of the download request (NSData *). If its value is nil, then an error occurred during the download and this is logged to the console; otherwise, the download was successful and the number of bytes downloaded is logged to the console.

if (data == nil)
{
  NSLog(@"Error loading request %@", [error localizedDescription]);
}
else
{
  NSLog(@" Downloaded %lu bytes from request %@",
        [data length], [request URL]);
}
downloadComplete = YES;

The next statement is a loop used to keep the application running until the connection has finished loading the resource.

while (!downloadComplete &&
       [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

This statement was used in examples in previous chapters. To summarize, it runs the loop, receiving events from its input sources and executing any corresponding delegate or callback methods until the connection has finished loading the resource.

Now save, compile, and run the BlockURLLoader program and observe the messages in the output pane (as shown in Figure 15-4).

9781430250500_Fig15-04.jpg

Figure 15-4. Testing the BlockURLLoader project

The messages in the output pane show that the URL was successfully downloaded and processed using the block literal expression. It displays the number of bytes downloaded and the URL for the request. Several Foundation Framework classes (e.g., NSURLConnection), Cocoa Framework classes, and various third-party APIs contain methods that use a block literal to perform callback functions, so it is very important to become familiar with this programming model. All right, enough said. Now let’s conclude this whirlwind tour with another program that uses a block to perform concurrent programming.

Concurrent Programming Using Blocks

You’ll finish this chapter with a program that uses a block to perform concurrent programming. This program employs GCD to concurrently execute several tasks defined with blocks. In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify BlockConcurrentTasks for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

In the Xcode project navigator, select the main.m file and update it, as shown in Listing 15-20.

Listing 15-20.  BlockConcurrentTasks main.m File

#import <Foundation/Foundation.h>
#define YahooURL       @"http://www.yahoo.com/index.html"
#define ApressURL       @"http://www.apress.com/index.html"

typedef void (^DownloadURL)(void);

/* Retrieve a block used to download a URL */
DownloadURL getDownloadURLBlock(NSString *url)
{
  NSString *urlString = url;
  return ^{
    // Downloads a URL
    NSURLRequest *request = [NSURLRequest
                             requestWithURL:[NSURL URLWithString:urlString]];
    NSError *error;
    NSDate *startTime = [NSDate date];
    NSData *data = [NSURLConnection sendSynchronousRequest:request
                                         returningResponse:nil
                                                     error:&error];
    if (data == nil)
    {
      NSLog(@"Error loading request %@", [error localizedDescription]);
    }
    else
    {
      NSDate *endTime = [NSDate date];
      NSTimeInterval timeInterval = [endTime timeIntervalSinceDate:startTime];
      NSLog(@"Time taken to download %@ = %f seconds", urlString, timeInterval);
    }
  };
}

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Create queues for tasks
    dispatch_queue_t queue1 =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue2 =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // Create a task group
    dispatch_group_t group = dispatch_group_create();
    
    // Get current time for metrics
    NSDate *startTime = [NSDate date];
    
    // Now create and dispatch async tasks
    dispatch_group_async(group, queue1, getDownloadURLBlock(YahooURL));
    dispatch_group_async(group, queue2, getDownloadURLBlock(ApressURL));
    
    // Block until all tasks from group are completed
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    // Retrieve time taken for concurrent execution and log
    NSDate *endTime = [NSDate date];
    NSTimeInterval timeInterval = [endTime timeIntervalSinceDate:startTime];
    NSLog(@"Time taken to download URLs concurrently = %f seconds", timeInterval);
  }
  return 0;
}

Listing 15-20 includes, in addition to the main() function, a function named getDownloadURLBlock that returns a block literal used to download a URL. The file begins by defining two constants, YahooURL and ApressURL, which name the URLs to be downloaded. Next, a type definition is provided for the block type to be used. The file then defines the getDownloadURLBlock() function. This function returns a block literal used to synchronously download a URL via the NSURLConnection method sendSynchronousRequest:returningResponse:error. It also computes the amount of time taken to download the URL using several NSDate APIs. The interval is computed as an NSTimeInterval value. Note that the block captures the URL string (provided as an argument to the function) using lexical scoping.

The main() function uses GCD APIs to create and dispatch two tasks for concurrent execution. It first creates two global concurrent dispatch queues for executing tasks in parallel.

dispatch_queue_t queue1 =
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue2 =
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Next, it creates a task group, which is used to group tasks and is useful for queuing tasks to perform asynchronous execution, waiting on a group of queued tasks, and so forth. The code then dispatches the two tasks to the task group.

dispatch_group_async(group, queue1, getDownloadURLBlock(YahooURL));
dispatch_group_async(group, queue2, getDownloadURLBlock(ApressURL));

Notice that the task to be executed is specified by a block literal retrieved by the getDownloadURLBlock() function; one using the Yahoo URL and the other using the Apress URL. The GCD dispatch_group_async() function causes these tasks to be performed concurrently. Next, the GCD dispatch_group_wait() function is used to block the main thread until both concurrent tasks complete.

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

Finally, the amount of time taken to complete the two concurrent tasks is computed and logged to the console. Now save, compile, and run the BlockConcurrentTasks program and observe the messages in the output pane (as shown in Figure 15-5).

9781430250500_Fig15-05.jpg

Figure 15-5. Testing the BlockConcurrentTasks project

The messages in the output pane show that the concurrent tasks were performed successfully. It also shows that the elapsed time to perform the tasks concurrently was less than what would have been taken to perform them sequentially. Cool. You just learned how to use blocks to perform concurrent programming and also received a brief introduction to the Grand Central Dispatch system. Note that Chapter 17 provides a complete introduction on the use of GCD to perform concurrent programming.

Roundup

In this chapter, you learned how to program with blocks, a powerful feature now available for the Objective-C language. It explored block syntax and semantics, memory management, how to develop blocks in your own code, and how to use blocks in existing APIs. The following are the key takeaways from this chapter:

  • Blocks are similar to functions and methods, but in addition to executable code, they may also contain variable bindings to stack or heap memory. A block is an implementation of a closure, a function that allows access to variables outside its typical scope.
  • Blocks can be declared as variables of block type, and block variables can be used anywhere standard variables can—as parameters to a function/method, and so forth.
  • A block literal expression contains the code invoked by a block. It can be defined inline, as an anonymous function, and thereby serve as the parameter for a function/method invocation.
  • A block augments the visibility of variables compared to C functions through lexical scoping, __block variables, instance variable access, and const imports.
  • At runtime, a block literal expression is allocated on the stack and thus has the same lifetime as a local variable. As a result, it must be copied to permanent storage (i.e., onto the heap) to be used outside of the scope in which it is defined.
  • The Block_copy() and Block_release() operations are used to copy/release blocks on the heap when using MRR memory management. With ARC memory management, the compiler automatically performs the block copy and release operations as long as the block does not return an id type or pass an id type as a parameter.
  • Blocks are most naturally used to implement small, self-contained pieces of code that encapsulate units of work. They are typically executed concurrently over the items of a collection or as a callback when an operation has finished. Because blocks can be defined inline, they can eliminate the need to create entirely separate classes and methods for context-sensitive code, such as asynchronous completion handlers. They also enable related code to be kept together, and not scattered among multiple files.

This was a detailed chapter that exposed you to a variety of advanced topics: blocks, concurrent programming, and the GCD APIs. Spend a little time to go over everything covered here. Feel free to modify the examples to learn more about these concepts. In the next chapter, you will learn all about Objective-C literals, another recent addition to the Objective-C language.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.123.106