4

我正在调用一个方法,该方法将枚举一个数组,创建一个 NSURL,并调用一个返回 JSON 的 NSURLSessionDataTask。循环通常运行大约 10 次,但可能会因日期而异。

在开始处理数据之前,我需要等待 for 循环和所有 NSURLSessionDataTasks 完成。

我很难确定所有工作何时完成。谁能推荐任何方法或逻辑来知道整个方法何时完成(用于循环和数据任务)?

-(void)findStationsByRoute{
for (NSString *stopID in self.allRoutes) {
    NSString *urlString =[NSString stringWithFormat:@"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%@", stopID];
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request
                                                 completionHandler:^(NSData *data,
                                                                     NSURLResponse *response,
                                                                     NSError *error) {
                                                     NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                                                     if(httpResponse.statusCode == 200){
                                                         NSError *jsonError = [[NSError alloc]init];
                                                         NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
                                                         NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:@"direction"];
                                                         for (NSDictionary * _stopDictionary in stopDirectionArray) {
                                                             NSArray *stop =   [_stopDictionary objectForKey:@"stop"];
                                                             [self.arrayOfStops addObject:stop];

                                                         }
                                                     }

                                                 }];

    [task resume];
}

}

4

2 回答 2

5

有多种选择。根本问题是这些单独的数据任务异步运行,因此您需要一些方法来跟踪这些异步任务并建立对它们完成的一些依赖关系。

有几种可能的方法:

  1. 典型的解决方案是使用调度组。在开始请求之前输入组dispatch_group_enter,将组留在dispatch_group_leave异步调用的完成处理程序中,然后在循环结束时提供一个dispatch_group_notify块,当所有“输入”调用时,该块将被异步调用被相应的“离开”调用抵消:

    - (void)findStationsByRoute {
        dispatch_group_t group = dispatch_group_create();
    
        for (NSString *stopID in self.allRoutes) {
            NSString     *urlString = [NSString stringWithFormat:@"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%@", stopID];
            NSURL        *url       = [NSURL URLWithString:urlString];
            NSURLRequest *request   = [NSURLRequest requestWithURL:url];
    
            dispatch_group_enter(group);   // enter group before making request
    
            NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                if(httpResponse.statusCode == 200){
                    NSError *jsonError;   // Note, do not initialize this with [[NSError alloc]init];
                    NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
                    NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:@"direction"];
                    for (NSDictionary *stopDictionary in stopDirectionArray) {
                        NSArray *stop = [stopDictionary objectForKey:@"stop"];
                        [self.arrayOfStops addObject:stop];
                    }
                }
    
                dispatch_group_leave(group);  // leave group from within the completion handler
            }];
    
            [task resume];
        }
    
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // do something when they're all done
        });
    }
    
  2. 处理此问题的更复杂的方法是将 包装NSSessionDataTaskNSOperation子类中,然后您可以使用数据任务操作和最终完成操作之间的依赖关系。您需要确保您的个人数据任务操作是“并发”的(即isFinished在异步数据任务完成之前不要发出通知)。这种方法的好处是您可以设置maxConcurrentOperationCount限制在任何给定时间将启动多少个请求。通常,您希望一次将其限制为 3-4 个请求。

    请注意,这也可以解决调度组方法可能遭受的超时问题。调度组不限制在任何给定时间提交的请求数量,而这很容易通过NSOperation.

    有关更多信息,请参阅并发编程指南的操作队列部分中有关“并发操作”的讨论。

    NSURLSessionTask有关在异步子类中包装请求的示例NSOperation,请参见后半部分NSURLSession 与 NSBlockOperation 和 queues的简单实现。这个问题正在解决一个不同的主题,但我NSOperation在最后包含了一个子类示例。

  3. 如果您使用上传/下载任务而不是数据任务,那么您可以使用[NSURLSessionConfiguration backgroundSessionConfiguration]and URLSessionDidFinishEventsForBackgroundURLSession:NSURLSessionDelegate然后在所有任务完成并将应用程序带回前台时调用。(不过,有点烦人的是,只有在下载完成时您的应用程序未处于活动状态时才会调用此方法:我希望即使下载完成时应用程序在前台也能调用此委托方法的再现。)

    当您询问数据任务(不能与后台会话一起使用)时,将后台会话与上传/下载任务一起使用具有后台操作的显着优势。如果您的过程确实需要 10 分钟(这看起来很特别),那么为后台会话重构它可能会带来显着的优势。

  4. 我什至不想提及这一点,但为了完整起见,我应该承认,理论上你可以通过维护一个可变数组或待处理数据任务的字典,并在完成每个数据任务后,从该列表中删除一个项目,并且,如果它断定它是最后一个任务,则手动启动完成过程。

于 2013-12-23T04:51:41.977 回答
3

查看dispatch groups,对于您的示例,它大致如下所示:

 create group
 for (url in urls)
     enter group
     start_async_task
         when complete leave group

 wait on group to finish or supply a block to be run when completed
于 2013-12-23T00:59:07.713 回答