Grand Central Dispatch And Writing Methods That Accept Blocks As Arguments

Have you ever looked at the enumerateObjectsUsingBlock method of NSArray and wondered how you might use that design pattern in your own application. In this tutorial, I will be showing you how to do just this. We will be writing methods that accept blocks as arguments as well as learning how to declare block ivars and properties.

This tutorial assumes that you have at least a basic understanding of using blocks and Grand Central Dispatch (GCD). If not, @cruffenach has created a more basic intro to blocks tutorial here if you need a refresher on them.

When You Might Want To Pass A Block To A Method

There are quite a few times when you might want to pass a block to a method. The most common reason is to replace the “delegate” design pattern. Basically, when you want to process some data (usually in another thread) and it needs to call back to the caller when it completes. This pattern is also useful when you want to have a method “return” multiple values. (Note: return is in quotes, because we are not actually returning multiple values but calling back using the block and passing multiple values).

Another great reason to use blocks in this way is it keeps your code flowing in a much more logical order. Rather than having this feeling of spaghetti code with callbacks jumping all over the place, all of your callback code can be written inline with everything else.

In the following example, we are going to (asynchronously) fetch a very large array of strings (in plist form) from the web and load it up into a UITableView. We will pass a simple completion block to our array fetch method which will tell our class to reload the table.

Creating Our Dictionary Downloader Class

We’ll start by creating a new class called PListDownloader. This will contain a single method called downloadPlistForURL. Declare it like this in PListDownloader.h.

- (void) downloadPlistForURL:(NSURL *) url completionBlock:(void (^)(NSArray *data, NSError *error)) block;

Let’s break this down. First, the method is void (no return). That’s because our block will handle all of the data that this method “returns”. The first parameter is simply the URL of the plist file. In our case, we will be passing in http://www.icodeblog.com/samples/block_test/block_test.plist . This is a ~3MB plist file that should take a couple seconds to download and process.

Finally, we pass in our block. Let’s break the block down even further.

1. “(void ” The void denotes that the block doesn’t return any value.

2. “(^))” The ^ without anything next to it denotes that it’s an anonymous block. We will go over naming our blocks in a bit.

3. (NSArray *data, NSError *error)) is what will be passed back into the block when it gets called. This would be equivalent to the “return values” in this case.

4. “block” This is simply the name of our block parameter passed in. You could have named it anything really.

It’s time for the implementation of this method. In the PListDownloader.m file add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) downloadPlistForURL:(NSURL *) url completionBlock:(void (^)(NSArray *data, NSError *error)) block {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul), ^{
        NSArray *returnArray = [NSArray arrayWithContentsOfURL:url];
        if(returnArray) {
            block(returnArray, nil);
        } else {
            NSError *error = [NSError errorWithDomain:@"plist_download_error" code:1 
                                             userInfo:[NSDictionary dictionaryWithObject:@"Can't fetch data" forKey:NSLocalizedDescriptionKey]];
            block(nil, error);
        }
 
    });
}

At first glance, one might argue that this looks “sloppier” than the delegate style. Simply because it uses nested blocks. I can assure you this is much cleaner AND simpler. For one, by using GCD you no longer have to worry about creating auto release pools.

So, there’s quite a bit going on here, let’s break it down line by line.

2. This is the Grand Central Dispatch way of firing off a code segment inside of a new thread. Basically we are executing this code on a new thread with a priority of HIGH. You can use this first parameter to prioritize the execution of multiple threads. For example say you are running 2 concurrent threads (one to download user data, the other to download the avatar). You can assign the user data thread higher priority than the avatar thread. The system will handle the rest.

**Note: Using Blocks here replaces the delgate method style of performSelectorInBackground

3. This is a very simple line to download plist data from a URL into an NSArray. There is no error checking or anything fancy. In production, you would want to use NSURLConnection or a lib built on top of it like ASIHTTPRequest.

4. Very simple error check, basically seeing if there was any data downloaded.

5. This is where the magic happens. We call this block as a simple function call passing in our returned array and nil for the error (nil since there was no error).

6-11. This segment handles our error case. If for some reason, no data was downloaded, we create our own NSError object and pass it into the exact same block as in line 5. The only difference is we pass in nil for the results and our error for the NSError object.

Before you gripe in the comments “You could have just done that async call in your main class…blah…blah…”, know that I know this. However, this is just a trivial example. Often times you might have a much larger codebase built up that does more than downloads a stupid plist. Perhaps you have a ton of preprocessing of the url or post-processing of the data. Then it starts to make less and less sense to put this code in your main implementation file.

Also, you might need to download plist files in various parts of the application and do different things with the data that was returned.

Calling A Method With A Block

So, now we have our simple class to download our plist and return the data in a block. But, how do we use it?

Let me give you a bit of context about my sample application first. I have a basic “Navigation Based” project with a NSArray property called pListData. I am displaying the contents of pListData in the UITableView. So, I want to download the plist data from the web, set it to my pListData property and reload my table.

First import the PListDownloader class and then uncomment the viewDidLoad method adding the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
    [super viewDidLoad];
    PListDownloader *downloader = [[[PListDownloader alloc] init] autorelease];
    NSURL *url = [NSURL URLWithString:@"http://www.icodeblog.com/samples/block_test/block_test.plist"];
    [downloader downloadPlistForURL:url completionBlock:^(NSArray *data, NSError *error) {
        self.plistData = data;
        if(!error) {
            dispatch_sync(dispatch_get_main_queue(), ^(void) {
                [self.tableView reloadData];
            });
        } else {
            NSLog(@"error %@", error);
        }
    }];
}

Again, let’s take a look at it line by line.

3. This should be familiar, we create a new instance of PListDownloader. One thing to note here is we are able to set it to autorelease. It won’t be released until AFTER the execution finishes and the thread dies. This is a HUGE improvement over the async delegate model where you would have to keep a property laying around.

4. Simply creating an NSURL object using the string location of our test plist (it’s still there, you can test with it)

5. This is where we actually call our downloadPListForURL method. The first parameter is the easy one, just the URL you created on line 4. The second is the the block that the method will call back to when execution finishes. Luckily XCode will auto complete a nice template for us.

6. Once the method calls back and contains an array of strings, we set our property (pListData) to the returned array. This will allow use to actually do something with the data.

7. Our simple error check

8. It’s going to get crazy again. Since we used GCD to run our code in a separate thread, we need a way to come back to the main thread to update the UI. If you are reading this and don’t know that you do all UI manipulations in the main thread, then I suggest you start following my “Back To Basics” tutorial series.

9. Now that we are in the context of the main thread, we can simply reload the tableview.

Now, when you run this application, it should take quite some time to fetch that data (3MB). But, you will have complete control over the UI while it’s fetch in the background. You could even throw up a little loading message if you so choose.

For completeness, here is a look at what this application produces.

 

As this post has gotten a little longer than I anticipated, I’ll save the part about declaring blocks as properties until next time. If you have any questions or comments, feel free to leave them in the comments of this post (or write them to me on Twitter).

You can download the source for this tutorial here.

Happy iCoding!

Leave a Reply

Your email address will not be published. Required fields are marked *