如何在NSURLSession中使用NSOperationQueue?

时间:2014-02-20 20:24:02

标签: ios objective-c nsoperation nsoperationqueue nsurlsession

我正在尝试构建一个批量图像下载器,可以将图像动态添加到队列中进行下载,我可以找到进度以及下载时间。

通过我的阅读,队列功能似乎NSOperationQueue,网络功能的NSURLSession似乎是我最好的选择,但我很困惑如何将两者结合使用。< / p>

我知道我将NSOperation的实例添加到NSOperationQueue并排队等候。似乎我用NSURLSessionDownloadTask创建了一个下载任务,如果我需要多个任务,我会创建多个任务,但我不确定如何将两者放在一起。

NSURLSessionDownloadTaskDelegate似乎拥有下载进度和完成通知所需的所有信息,但我还需要能够停止特定下载,停止所有下载,并处理我从中获取的数据下载。

7 个答案:

答案 0 :(得分:45)

你的直觉是正确的。如果发出许多请求,NSOperationQueue maxConcurrentOperationCount为4或5可能非常有用。如果没有这种情况,如果您发出许多请求(例如,50个大图像),则在处理慢速网络连接(例如某些蜂窝连接)时可能会遇到超时问题。操作队列也有其他优点(例如依赖关系,分配优先级等),但控制并发度是关键的好处,恕我直言。

如果您使用基于completionHandler的请求,那么实现基于操作的解决方案非常简单(它是典型的并发NSOperation子类实现;请参阅配置并发执行操作并发编程指南Operation Queues章的部分以获取更多信息。)

如果您正在使用基于delegate的实现,那么事情会很快变得非常毛茸茸。这是因为NSURLSession的一个可理解的(但令人难以置信的烦人)特性,即任务级代表在会话级实现。 (考虑一下:需要不同处理的两个不同请求是在共享会话对象上调用相同的委托方法.Egad!)

在一个操作中包装一个基于委托的NSURLSessionTask可以完成(我和其他人,我确定已经完成了),但它涉及一个让会话对象维护一个字典的笨重过程使用任务操作对象交叉引用任务标识符,让它传递传递给任务对象的这些任务委托方法,然后使任务对象符合各种NSURLSessionTask委托协议。它需要相当多的工作量,因为NSURLSession在会话中没有提供maxConcurrentOperationCount样式的功能(更不用说其他NSOperationQueue优点了,比如依赖项,完成块等。)。

值得指出的是,基于操作的实现对于后台会话来说是一个非启动性的。应用程序终止后,您的上传/下载任务将继续运行良好(这是一件好事,这是后台请求中相当重要的行为),但是当您的应用程序重新启动时,操作队列和所有它的运作已经消失。因此,您必须为后台会话使用基于委托的纯NSURLSession实现。

答案 1 :(得分:38)

从概念上讲,NSURLSession是一个操作队列。如果在完成处理程序上恢复NSURLSession任务和断点,则堆栈跟踪可能非常明显。

以下是关于NSURLSession的忠实Ray Wenderlich教程的摘录,并在执行完成处理程序时添加了NSLog语句来断点:

NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
          completionHandler:^(NSData *data,
                              NSURLResponse *response,
                              NSError *error) {
            // handle response
            NSLog(@"Handle response"); // <-- breakpoint here       

  }] resume];

NSOperationQueue Serial Queue breakpoint

上面,我们可以看到正在Thread 5 Queue: NSOperationQueue Serial Queue执行完成处理程序。

所以,我的猜测是每个NSURLSession维护它自己的操作队列,并且添加到会话的每个任务都是 - 在引擎盖下 - 作为NSOperation执行。因此,维护控制NSURLSession对象或NSURLSession任务的操作队列是没有意义的。

NSURLSessionTask本身已经提供了等效的方法,例如cancelresumesuspend等。

确实,你对自己的NSOperationQueue的控制力要低于你自己的NSOperationQueue。但话又说回来,NSURLSession是一个新课程,其目的无疑是为了减轻你的负担。

底线:如果您想减少麻烦 - 但控制力较少 - 并且相信Apple会代表您胜任执行网络任务,请使用NSURLSession。否则,使用NSURLConnection和您自己的操作队列滚动自己的。

答案 2 :(得分:5)

更新executingfinishing属性可以了解当前NSOperation的状态。将finishing设置为YESexecuting设置为NO后,您的操作即被视为已完成。处理它的正确方法不需要dispatch_group,可以简单地写成异步NSOperation

  - (BOOL) isAsynchronous {
     return YES;
  }

  - (void) main
    {
       // We are starting everything
       self.executing = YES;
       self.finished = NO;

       NSURLSession * session = [NSURLSession sharedInstance];

       NSURL *url = [NSURL URLWithString:@"http://someurl"];

       NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

          /* Do your stuff here */

         NSLog("Will show in second");

         self.executing = NO;
         self.finished = YES;
       }];

       [dataTask resume]
   }

术语asynchronous非常具有误导性,并不是指UI(主)线程和后台线程之间的区别。

如果isAsynchronous设置为YES则表示代码的某些部分是以main方法异步执行的。换句话说:main方法内进行异步调用,方法将在主方法完成后完成

我有一些关于如何在apple os上处理并发的幻灯片:https://speakerdeck.com/yageek/concurrency-on-darwin

旧回答:您可以尝试dispatch_group_t。您可以将它们视为GCD的保留计数器。

想象一下main子类的NSOperation方法中的代码:

- (void) main
{

   self.executing = YES;
   self.finished = NO;

   // Create a group -> value = 0
   dispatch_group_t group = dispatch_group_create();

   NSURLSession * session = [NSURLSession sharedInstance];

   NSURL *url = [NSURL URLWithString:@"http://someurl"];

    // Enter the group manually -> Value = Value + 1
   dispatch_group_enter(group); ¨

   NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){


      /* Do your stuff here */

      NSLog("Will show in first");

      //Leave the group manually -> Value = Value - 1
      dispatch_group_leave(group);
   }];

   [dataTask resume];

  // Wait for the group's value to equals 0
  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

  NSLog("Will show in second");

  self.executing = NO;
  self.finished = YES;
}

答案 3 :(得分:2)

使用NSURLSession,您不会手动将任何操作添加到队列中。您可以在NSURLSession上使用方法- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request生成一个数据任务,然后启动它(通过调用resume方法)。

您可以提供操作队列,以便您可以控制队列的属性,并在需要时将其用于其他操作。

您希望对数据任务执行的NSOperation(即启动,暂停,停止,恢复)采取的任何常规操作。

要排队50张图片进行下载,您只需创建50个数据任务,NSURLSession将正确排队。

答案 4 :(得分:0)

如果您正在使用OperationQueue并且不希望每个操作同时创建许多网络请求,则可以在将每个操作添加到队列后调用queue.waitUntilAllOperationsAreFinished()。它们现在只在前一个完成后执行,大大减少了同时网络连接的数量。

答案 5 :(得分:0)

这是一个使用NSOperation和NSURLSession的示例项目: https://github.com/MacMark/Operations-Demo

对于后台会话,您可以通过在此回调中使用会话的标识符来恢复NSOperation:

!(versionBatch == self->versionBatches.rend())

答案 6 :(得分:-1)

也许你正在寻找这个:

http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/

这不是'内置'有点奇怪,但是如果你想用NSOperation连接NSURL的东西,看起来你必须在主线程中重用runloop并使操作成为'并发'的操作('并发'到队列中。)

虽然在你的情况下 - 如果只是简单的下载,没有后续的,依赖的,操作连接 - 我不确定你使用NSOperation会获得什么。