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:
We are going to launch a block object asynchronously on a concurrent queue.
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.
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.