I’ve got this parsing operation that currently works fine, but I’ve started to notice that it is freezing up my UI slightly so I’m trying to refactor and get this done asynchronously. I’m having some issues however and was hoping someone could point me in the right direction. Here’s my current (synchronous) code:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
return nil;
}
NSArray *events = [eventsData valueForKeyPath:@"resultsPage.results"];
if (events == nil) {
//parsing error
return nil;
}
NSLog(@"events looks like %@", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:@"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:@"location.lat"];
event.longitude = [object valueForKeyPath:@"location.lng"];
event.title = [object valueForKeyPath:@"displayName"];
event.venue = [object valueForKeyPath:@"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:@"uri"]];
event.artist = [object valueForKeyPath:@"performance.artist.displayName"];
event.date = [object valueForKeyPath:@"start.datetime"];
[formattedEvents addObject:event];
}
return [NSArray arrayWithArray:formattedEvents];
}
I’ve been looking into NSOperationQueue’s and I’m struggling to find a solution as I’d like to return an array from this method and operation queues are not meant to have return values. I’m also looking at GCD and i’ve got somethinbg like this:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSMutableArray *mutable = [NSMutableArray array];
dispatch_async(backgroundQueue, ^{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
mutable = nil;
}
NSArray *events = [eventsData valueForKeyPath:@"resultsPage.results"];
if (events == nil) {
//parsing error
mutable = nil;
}
NSLog(@"events looks like %@", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:@"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:@"location.lat"];
event.longitude = [object valueForKeyPath:@"location.lng"];
event.title = [object valueForKeyPath:@"displayName"];
event.venue = [object valueForKeyPath:@"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:@"uri"]];
event.artist = [object valueForKeyPath:@"performance.artist.displayName"];
event.date = [object valueForKeyPath:@"start.datetime"];
[formattedEvents addObject:event];
}
mutable = [NSMutableArray arrayWithArray:formattedEvents];
});
return [mutable copy];
}
For some reason, this seems to be returning the object before the parsing has finished however, as I’m gettting no data out of that mutable object, but I’m noticing that the parsing is indeed occurring (i’m logging out the results). can anyone give me an idea about how to get this asynch stuff going?
Thanks!!
You primary problem is that by their very nature asynchronous operations can’t synchronously return a result. Instead of returning an array from
-eventsFromJSON:, you should provide a way for the caller to receive a callback when the results are finished. There are two common approaches to this in Cocoa.You can create a delegate with an associated delegate protocol including a method like
-parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events, then have your parser call this method on its delegate when parsing is finished.Another solution is to allow the caller to provide a completion block to be executed when parsing is complete. So, you might do something like this:
Then you can call this code some thing like this:
I like the second, block-based approach better. It makes for less code in most cases. The code also reads closer to the synchronous approach where the method just returns an array, since the code that uses the resultant array simply follows the method call (albeit indented since it’s in the completion block’s scope).