Brief Intro to Blocks 5:Using Blocks

来源:互联网 发布:苹果办公软件三件套 编辑:程序博客网 时间:2024/05/21 14:06

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 Assistantpane, create a command-line application. In the Project Options window, specify BlockArraySorterfor 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 10int 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 NSMutableArrayobject 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 typeNSComparisonResultNSComparisonResult 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 appropriateNSComparisonResult to sort the array elements based on ascending (numerical) order, as shown inListing 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 (NSArrayNSDictionary) 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 imageProject . . . 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, chooseFoundation 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(@"\n\tDownloaded %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, anNSURLRequest 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 NSOperationQueueinstance 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(@"\n\tDownloaded %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, specifyBlockConcurrentTasks 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 getDownloadURLBlockthat 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 anNSTimeInterval 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 thegetDownloadURLBlock() 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 thatChapter 17 provides a complete introduction on the use of GCD to perform concurrent programming.

原创粉丝点击