0

I'm writing an app which utilizes a UITabBarController to switch views. One tab makes a web request to gather and then process JSON data before populating a UITableView. I am attempting to load this data in the background so when the user presses the tab, there is no delay in presenting the table. An ActivityIndicator will bed displayed and then the table is loaded with data.

Each request is made, processed and results placed in an NSMutableArray which is then sorted and added to the UITableView.

When I use dispatch_sync, the data is loaded and the array is created and presented fine, however the UI for the view is blocked from loading. I'm thinking because for some reason I'm not getting this query to a background queue. If I use dispatch_async, I get exceptions when an attempt is made to access the NSMutableArray on the main thread.

So, my question is what is the proper pattern for allowing the user to switch to the tab which contains this TableView, present an ActivityIndicator while the data loads and processed (in the background) and then once completed, the UITableView is loaded with the processed data.

Relavent code from the UITableViewController:

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

- (void)viewDidLoad
{
    [super viewDidLoad];

    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    currentLat = appDelegate.currentLatitude;
    currentLong = appDelegate.currentLongitude;

    finalDistanceArray = [[NSMutableArray alloc] init];

    [self compareLocationMMC];
    [self compareLocationLVMC];
    [self compareLocationSBCH];
    [self compareLocationGVCH];
    [self compareLocationSYVCH];

    NSSortDescriptor *lowestToHighest = [NSSortDescriptor sortDescriptorWithKey:@"distance" ascending:YES];

    [hospitalList sortUsingDescriptors:[NSArray arrayWithObject:lowestToHighest]];  

}

- (void)compareLocationMMC {


NSString * searchURL = [NSString stringWithFormat:@"http://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=true", currentLat.floatValue, currentLong.floatValue, MMC_LAT, MMC_LON];
NSURL * myURL = [NSURL URLWithString:searchURL];

dispatch_sync(kBgQueue, ^{
    NSData* data = [NSData dataWithContentsOfURL: 
                    myURL];
     [self performSelectorOnMainThread:@selector(fetchedData:) 
                          withObject:data waitUntilDone:YES];
});


}
//The compareLocationXXX method is repeated 4 more times with different search strings

- (void)fetchedData:(NSData *)responseData {

//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization 
                      JSONObjectWithData:responseData 

                      options:kNilOptions 
                      error:&error];

NSArray* stationDistance = [json objectForKey:@"routes"]; 

NSDictionary* legs = [stationDistance objectAtIndex:0];

NSArray* legsBetween = [legs objectForKey:@"legs"];

NSDictionary* distanceBetween = [legsBetween objectAtIndex:0];

finalDistance = [distanceBetween valueForKeyPath:@"distance.text"];

[finalDistanceArray addObject:finalDistance];

}
Monolo
  • 18,205
  • 17
  • 69
  • 103
blueHula
  • 1,581
  • 1
  • 9
  • 16

1 Answers1

0

Dispatch_sync() will in fact block the thread it's called from since it halts execution to enqueue a block. Dispatch_async is a much better choice since it allows the calling function to resume. You can wrap one call inside another to allow you to execute completion code on the main thread. This also makes the codes execution extremely easy to read.

// Turn on status bar spinner, for example
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // Do background stuff here
        ....
    NSData* data = [NSData dataWithContentsOfURL:myURL];
    // used parsed data to populate data structure of some type
        ....
    dispatch_async(dispatch_get_main_queue(), ^{
        // Use background stuff here
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    });
});

While a callback based API like NSURLConnection is probably best for network loading, this will work fairly well. Especially for small requests.

NJones
  • 27,139
  • 8
  • 70
  • 88
  • Thanks! I believe I have this working fine on the background, but the subsequent code accesses the NSMutableArray and is doing so before the data is loaded causing an exception. How do I essentially wait to ensure the data is loaded, and sorted before accessing it? – blueHula Feb 15 '12 at 19:57
  • You shouldn't be using the data you load until the "Use background stuff here" part. Which would likely be copying your fetched data and calling `reloadTable` [This answer I posted is relevant to your new problem.](http://stackoverflow.com/a/8962406/927947) – NJones Feb 16 '12 at 04:35