如何知道使用NSURLSessionDataTasks的for循环何时完成

4

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

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

我很难弄清楚何时完成所有工作。是否有人能推荐任何方法或逻辑,以知道整个方法何时完成(包括for循环和数据任务)?

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

}


你的任务已经在completionHandler块中完成。 - johnMa
2个回答

5

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

有几种可能的方法:

  1. The typical solution is to employ a dispatch group. Enter the group before you start the request with dispatch_group_enter, leave the group with dispatch_group_leave inside the completion handler, which is called asynchronously, and then, at the end of the loop, supply a dispatch_group_notify block that will be called asynchronously when all of the "enter" calls are offset by corresponding "leave" calls:

    - (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. A more sophisticated way to handle this is to wrap the NSSessionDataTask in a NSOperation subclass and you can then use dependencies between your data task operations and your final completion operation. You'll want to ensure your individual data task operations are "concurrent" ones (i.e. do not issue isFinished notification until the asynchronous data task is done). The benefit of this approach is that you can set maxConcurrentOperationCount to constrain how many requests will be started at any given time. Generally you want to constrain it to 3-4 requests at a time.

    Note, this can also address timeout issues from which the dispatch group approach can suffer from. Dispatch groups don't constrain how many requests are submitted at any given time, whereas this is easily accomplished with NSOperation.

    For more information, see the discussion about "concurrent operations" in the Operation Queue section of the Concurrency Programming Guide.

    For an example of wrapping NSURLSessionTask requests in asynchronous NSOperation subclass, see a simple implementation the latter half NSURLSession with NSBlockOperation and queues. This question was addressing a different topic, but I include a NSOperation subclass example at the end.

  3. If instead of data tasks you used upload/download tasks, you could then use a [NSURLSessionConfiguration backgroundSessionConfiguration] and URLSessionDidFinishEventsForBackgroundURLSession: of your NSURLSessionDelegate would then get called when all of the tasks are done and the app is brought back into the foreground. (A little annoyingly, though, this is only called if your app was not active when the downloads finished: I wish there was a rendition of this delegate method that was called even if the app was in the foreground when the downloads finished.)

    While you asked about data tasks (which cannot be used with background sessions), using background session with upload/download tasks enjoys a significant advantage of background operation. If your process really takes 10 minutes (which seems extraordinary), refactoring this for background session might offer significant advantages.

  4. I hate to even mention this, but for the sake a completeness, I should acknowledge that you could theoretically just by maintain an mutable array or dictionary of pending data tasks, and upon the completion of every data task, remove an item from that list, and, if it concludes it is the last task, then manually initiate the completion process.


3

请看调度组,对于您的示例,代码大致如下:

 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

2
顺便提一下,如果您使用此技术,请确保使您的任务同步(例如使用信号量),否则您的组将在所有任务排队后“完成”,而不是在所有任务完成时。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接