Performing Non-UI-Related Tasks Asynchronously

This is where GCD can show its true power: executing blocks of code asynchronously on the main, serial, or concurrent queues. I promise that, by the end of this section, you will be completely convinced GCD is the future of multithread applications, completely replacing threads in modern apps.

In order to execute asynchronous tasks on a dispatch queue, you must use one of these functions:

dispatch_async

Submits a block object to a dispatch queue (both specified by parameters) for asynchronous execution.

dispatch_async_f

Submits a C function to a dispatch queue, along with a context reference (all three specified by parameters), for asynchronous execution.

Let’s have a look at a real example. We’ll write an iOS app that is able to download an image from a URL on the Internet. After the download is finished, the app should display the image to the user. Here is the plan and how we will use what we’ve learned so far about GCD in order to accomplish it:

  1. We are going to launch a block object asynchronously on a concurrent queue.

  2. Once in this block, we will launch another block object synchronously, using the dispatch_sync function, to download the image from a URL. Synchronously downloading a URL from an asynchronous code block holds up just the queue running the synchronous function, not the main thread. The whole operation still is asynchronous when we look at it from the main thread’s perspective. All we care about is that we are not blocking the main thread while downloading our image.

  3. Right after the image is downloaded, we will synchronously execute a block object on the main queue (see Performing UI-Related Tasks) in order to display the image to the user on the UI.

The skeleton for our plan is as simple as this:

dispatch_queue_t concurrentQueue =
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(concurrentQueue, ^{

  __block UIImage *image = nil;

  dispatch_sync(concurrentQueue, ^{
    /* Download the image here */
  });

  dispatch_sync(dispatch_get_main_queue(), ^{
    /* Show the image to the user here on the main queue*/
  });

});

The second dispatch_sync call, which displays the image, will be executed on the queue after the first synchronous call, which downloads our image. That’s exactly what we want, because we have to wait for the image to be fully downloaded before we can display it to the user. So after the image is downloaded, we execute the second block object, but this time on the main queue.

Let’s download the image and display it to the user now. We will do this in the viewDidAppear: instance method of a view controller displayed in an iPhone app:

- (void) viewDidAppear:(BOOL)paramAnimated{

  dispatch_queue_t concurrentQueue =
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  dispatch_async(concurrentQueue, ^{

    __block UIImage *image = nil;

    dispatch_sync(concurrentQueue, ^{
      /* Download the image here */

      /* iPad's image from Apple's website. Wrap it into two
       lines as the URL is too long to fit into one line */
      NSString *urlAsString = @"http://images.apple.com/mobileme/features"\
                              "/images/ipad_findyouripad_20100518.jpg";

      NSURL *url = [NSURL URLWithString:urlAsString];

      NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

      NSError *downloadError = nil;
      NSData *imageData = [NSURLConnection
                           sendSynchronousRequest:urlRequest
                           returningResponse:nil
                           error:&downloadError];

      if (downloadError == nil &&
          imageData != nil){

        image = [UIImage imageWithData:imageData];
        /* We have the image. We can use it now */

      }
      else if (downloadError != nil){
        NSLog(@"Error happened = %@", downloadError);
      } else {
        NSLog(@"No data could get downloaded from the URL.");
      }

    });

    dispatch_sync(dispatch_get_main_queue(), ^{
      /* Show the image to the user here on the main queue*/

      if (image != nil){
        /* Create the image view here */
        UIImageView *imageView = [[UIImageView alloc]
                                  initWithFrame:self.view.bounds];

        /* Set the image */
        [imageView setImage:image];

        /* Make sure the image is not scaled incorrectly */
        [imageView setContentMode:UIViewContentModeScaleAspectFit];

        /* Add the image to this view controller's view */
        [self.view addSubview:imageView];

        /* Release the image view */
        [imageView release];

      } else {
        NSLog(@"Image isn't downloaded. Nothing to display.");
      }

    });

  });

}

As you can see in Figure 2-2, we have successfully downloaded our image and also created an image view to display the image to the user on the UI.

Let’s move on to another example. Let’s say that we have an array of 10,000 random numbers that have been stored in a file on disk and we want to load this array into memory, sort the numbers in an ascending fashion (with the smallest number appearing first in the list), and then display the list to the user. The control used for the display depends on whether you are coding this for iOS (ideally, you’d use an instance of UITableView) or Mac OS X (NSTableView would be a good candidate). Since we don’t have an array, why don’t we create the array first, then load it, and finally display it?

Here are two methods that will help us find the location where we want to save the array of 10,000 random numbers on disk on the device:

- (NSString *) fileLocation{

  /* Get the document folder(s) */
  NSArray *folders =
  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                      NSUserDomainMask,
                                      YES);

  /* Did we find anything? */
  if ([folders count] == 0){
    return nil;
  }

  /* Get the first folder */
  NSString *documentsFolder = [folders objectAtIndex:0];

  /* Append the file name to the end of the documents path */
  return [documentsFolder
          stringByAppendingPathComponent:@"list.txt"];

}

- (BOOL) hasFileAlreadyBeenCreated{

  BOOL result = NO;

  NSFileManager *fileManager = [[NSFileManager alloc] init];
  if ([fileManager fileExistsAtPath:[self fileLocation]] == YES){
    result = YES;
  }
  [fileManager release];

  return result;
}

Now the important part: we want to save an array of 10,000 random numbers to disk if and only if we have not created this array before on disk. If we have, we will load the array from disk immediately. If we have not created this array before on disk, we will first create it and then move on to loading it from disk. At the end, if the array was successfully read from disk, we will sort the array in an ascending fashion and finally display the results to the user on the UI. I will leave displaying the results to the user up to you:

  dispatch_queue_t concurrentQueue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  /* If we have not already saved an array of 10,000
   random numbers to the disk before, generate these numbers now
   and then save them to the disk in an array */
  dispatch_async(concurrentQueue, ^{

    NSUInteger numberOfValuesRequired = 10000;

    if ([self hasFileAlreadyBeenCreated] == NO){
      dispatch_sync(concurrentQueue, ^{

        NSMutableArray *arrayOfRandomNumbers =
        [[NSMutableArray alloc] initWithCapacity:numberOfValuesRequired];

        NSUInteger counter = 0;
        for (counter = 0;
             counter < numberOfValuesRequired;
             counter++){
          unsigned int randomNumber =
          arc4random() % ((unsigned int)RAND_MAX + 1);

          [arrayOfRandomNumbers addObject:
           [NSNumber numberWithUnsignedInt:randomNumber]];
        }

        /* Now let's write the array to disk */
        [arrayOfRandomNumbers writeToFile:[self fileLocation]
                               atomically:YES];

        [arrayOfRandomNumbers release];

      });
    }

    __block NSMutableArray *randomNumbers = nil;

    /* Read the numbers from disk and sort them in an
     ascending fashion */
    dispatch_sync(concurrentQueue, ^{

      /* If the file has now been created, we have to read it */
      if ([self hasFileAlreadyBeenCreated] == YES){
        randomNumbers = [[NSMutableArray alloc]
                         initWithContentsOfFile:[self fileLocation]];

        /* Now sort the numbers */
        [randomNumbers sortUsingComparator:
         ^NSComparisonResult(id obj1, id obj2) {

           NSNumber *number1 = (NSNumber *)obj1;
           NSNumber *number2 = (NSNumber *)obj2;
           return [number1 compare:number2];

         }];
      }
    });


    dispatch_async(dispatch_get_main_queue(), ^{
      if ([randomNumbers count] > 0){
        /* Refresh the UI here using the numbers in the
         randomNumbers array */
      }
      [randomNumbers release];
    });

  });

There is a lot more to GCD than synchronous and asynchronous block or function execution. In Running a Group of Tasks Together you will learn how to group block objects together and prepare them for execution on a dispatch queue. I also suggest that you have a look at Performing Tasks After a Delay and Performing a Task at Most Once to learn about other functionalities that GCD is capable of providing to programmers.