It's a Dispatch Group Thing

One Thing Multiple Times

When it comes making iOS apps multi-threaded by now we should have at least heard of Grand Central Dispatch. The most common use case that is implemented and used is asynchronously fetching an image or web page, returning the resource…manipulating it in some fashion and the updating the main thread (UI) with updated resource.

Image Async Example

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0), ^{
    // fetch image from remote server
   dispatch_async(dispatch_get_main_queue(), ^{
     // set image for imageview
     // reload view if necessary...i.e. table view
   });
});

Copy and Paste Should Work Right?

For a project I’m working on I thought I would be able to utilize the same paradigm for the following scenario;

Grab a list of all our customers accounts. For any given customer they can have n+1 accounts and I don’t want them to run serially, but concurrently (our vendor has a POORLY design datastore so the queries take forever) to keep wait time to a minimum. Once all the calls for getAccountDetailsForAccountNumber have finished the tableview on the main view controller is refreshed with the info.

// AccountInfoViewController

- (void)viewWillAppear:(BOOL)animated {
      [super viewWillAppear:animated];

      [self.imf getAccountListMetaInformation:^(id obj, BOOL success, NSError *error, IMFClientErrorType clientError) {

          if (error) {
           // handle error
          } else {
            // NSArray *aggregatedInformation = (NSArray *)obj;
            //refresh tableView with aggregatedInformation
          }
    }];
 }

// IMFClient pseudo

@property (nonatomic, strong) NSMutableArray *willHoldAccountDetails

-call getAccountListMetaInformation
       // do general setup and check for cache
       // START CODE THAT SHOULD RUN CONCURRENTLY
      // BUT getAccountListMetaInformation COMPLETION BLOCK SHOULD WAIT
      -call getAccountDetailsForAccountNumber
         -run completion block
             - ADD return object from getAccountDetailsForAccountNumber to willHoldAccountDetails property
      -call getAccountDetailsForAccountNumber
        -run completion block
             - ADD return object from getAccountDetailsForAccountNumber to willHoldAccountDetails property
     -call getAccountDetailsForAccountNumber
        -run completion block
             - ADD return object from getAccountDetailsForAccountNumber to willHoldAccountDetails property
      // END CONCURRENT CODE - SAFE TO CALL COMPLETION BLOCK FOR getAccountListMetaInformation
-finish getAccountListMetaInformation
-willHoldAccountDetails is now populated and passed to completion block

First Attempt

I failed.

Why It Failed

The first reason my code failed was because of my failure to take the time and truly appreciate and understand what happens with custom queues, dispatch_* methods and how objects are managed by queues and threads. I tried to apply the same logic to a totally different scenario.

The second reason my code failed was getAccountListMetaInformation is running on the main thread, so none of getAccountDetailsForAccountNumber’s dispatch_async calls are going to be able to run until getAccountListMetaInformation returns. Anytime the code runs on the main (or any) thread, that’s going to block other code from running on that thread.

I regrouped….literally

I went back and rewatched the WWDC videos from 2010 and 2011 on Grand Central Dispatch, as well as, read over Mike Ash’s posts regarding GCD. Paying particularly close attention to Friday Q&A 2009-09-04: Intro to Grand Central Dispatch, Part II: Multi-Core Performance.

Winning with Simplicity

dispatch_group_async to the rescue. When going back and evaluating what I was trying to do, which is to iterate over list of accounts and fetch each one concurrently, and then updating the comletionBlock with aggregated array, the simpliest approach was to push each request on to a dispatch_group and then WAIT for all of them to complete.

Shocker…it worked.

Lesson Learned

The most important lesson I took away from this is regardless how much abstraction and convience that GCD provides it can’t/won’t abstract the business logic and having a firm understanding of the GCD paradigms are paramount.

Project

ThreadingHell. If you have a better/different implemenation then please let me know.

Forward Geocoding in iOS4 and iOS5

Forward Geocoding

It is a common scenario that every developer has run into.

Locate places near me based upon a particular address and/or zip code.

In order to get the correct results the address needs to be converted to a lat/long coordinate. With iOS5 Apple has provided us with CLGeoCoder class which has convience methods for reverse/forward geocoding. Unfortunately, there isn’t really any built-in functionality for iOS4. Most current apps still have to support iOS4 so what are developers doing to fill the gap? Write their implementations for multiple OS support. Below is what I use.

** Note: I did use Sergio Martínez-Losa Del Rincón article as a starting point for the iOS4 support.

Techniques

  • Google’s Map API
  • CLGeoCoder
  • Objective-C blocks for callback
  • Grand Central Dispatch

Implementation

iOS5

I tackled the easiest solution first. Utilizing iOS5’s CLGeoCoding geocodeAddressString:completionHandler: . However, when I first use the method I kept getting back a location of {0.0,0.0} in my log. The reason being that I was neglecting the fact that the method executes asynchronously and doesn’t return its value. Hmmm….

Not too much of a hurdle thanks to the implementation of block handling. By adding a completionHandler to my own method I was able to provide a callback with the appropriate coordinate that I need after the request had finished.

iOS4

Duplicating the ease of the iOS5 functionality was a bit more involved. I created a category method on NSString (see the above referenced article for code origin. I did make updates to it.) for making a call to the Google Maps API and return their forward coding result. The method itself isn’t asynchronous because I wanted to leave the decision of whether or not to call the method async or sync up to the developer. To provide asynchrous support was easy just by utiliizing GCD.

Decision Time

Deciding which code block should the calling method execute was the final task to finish. Once again this was extremely simple.

if (NSClassFromString(@“CLGeocoder”)) { // ios5 } else { // everything else. in this case we are assuming there isn’t support for iOS3.x }

Code