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.