简体   繁体   中英

Parsing asynchronously using NSOperationQueue or GCD

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:

- (void)eventsFromJSON:(NSString *)objectNotation completionHandler:(void (^)(NSArray *events))completionHandler)
{
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(backgroundQueue, ^{
        NSMutableArray *mutable = [NSMutableArray array];
        NSParameterAssert(objectNotation != nil);
        NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error = nil;

        // Snip...

        mutable = [NSMutableArray arrayWithArray:formattedEvents];

        dispatch_async(dispatch_get_main_queue(), ^{
            completionHandler([mutable copy]);
        });
    });
}

Then you can call this code some thing like this:

 - (void)parseJSONAndUpdateUI // Or whatever you're doing
{
    NSString *jsonString = ...;
    Parser *parser = [[Parser alloc] init];
    [parser parseEventsFromJSON:jsonString completionHandler:^(NSArray *events){
        // Update UI with parsed events here
    }];
}

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).

I would recommend using a completion block that you pass into your parse method. This way you don't have to return a value, but can do what you need to with the information once it is parsed. You just have to make sure you use GCD again to put the completion block on the main thread.

You could also post a notification on the main thread once the operation is complete that contains the array in userInfo.

Returning a value will not work however for asynchronous operations.

You are getting a returned object before the parsing has finished because your return [mutable copy] is outside of the dispatch_async block. Since dispatch_async functions asynchronously, it will return immediately, and then calls your return [mutable copy] (which is empty because it's not done parsing).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM