使用NSOperationQueue或GCD异步解析

时间:2012-10-22 18:13:49

标签: objective-c ios asynchronous grand-central-dispatch nsoperationqueue

我有这个解析操作,目前工作正常,但我已经开始注意到它稍微冻结了我的UI所以我正在尝试重构并异步完成此操作。然而,我遇到了一些问题,希望有人能指出我正确的方向。这是我当前的(同步)代码:

- (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];

}

我一直在研究NSOperationQueue,我正在努力寻找解决方案,因为我想从这个方法返回一个数组,而操作队列并不意味着有返回值。我也在看GCD,我有这样的想法:

- (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];
}

出于某种原因,这似乎是在解析完成之前返回对象,但是因为我没有从该可变对象中获取数据,但我注意到解析确实正在发生(我正在记录结果)。任何人都可以给我一个关于如何获得这个异步的东西的想法吗?

谢谢!

3 个答案:

答案 0 :(得分:3)

您的主要问题是,异性操作本质上不能同步返回结果。您应该为调用者提供一种在结果完成时接收回调的方法,而不是从-eventsFromJSON:返回数组。在Cocoa中有两种常见的方法。

您可以创建一个具有关联委托协议的委托,包括-parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events之类的方法,然后让解析器在解析完成时在其委托上调用此方法。

另一种解决方案是允许调用者提供在解析完成时要执行的完成块。所以,你可能会这样做:

- (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]);
        });
    });
}

然后你可以这样调用这段代码:

 - (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
    }];
}

我更喜欢第二种基于块的方法。在大多数情况下,它可以减少代码。代码也更接近于同步方法,其中方法只返回一个数组,因为使用结果数组的代码只是跟随方法调用(虽然缩进,因为它在完成块的范围内)。

答案 1 :(得分:1)

我建议您使用传递给解析方法的完成块。这样,您不必返回值,但可以在解析后使用信息执行所需操作。您只需确保再次使用GCD将完成块放在主线程上。

一旦完成包含userInfo中数组的操作,您也可以在主线程上发布通知。

但是对于异步操作,返回值将不起作用。

答案 2 :(得分:0)

在解析完成之前,您将获得返回的对象,因为返回[mutable copy]位于dispatch_async块之外。由于dispatch_async以异步方式运行,它将立即返回,然后调用返回[mutable copy](由于未进行解析,因此返回为空)。