ios 如何将NSOperationQueue与NSURLSession一起使用?

ruyhziif  于 2023-01-27  发布在  iOS
关注(0)|答案(7)|浏览(129)

我试图建立一个批量图像下载器,在那里图像可以添加到一个队列的飞行下载,我可以找出进度和当他们完成下载。
通过我的阅读,似乎NSOperationQueue用于队列功能,NSURLSession用于网络功能似乎是我最好的选择,但我对如何串联使用这两个功能感到困惑。
我知道我把NSOperation的示例添加到NSOperationQueue中,它们会排队,而且好像我用NSURLSessionDownloadTask创建了一个下载任务,如果我需要多个任务,我会用multiple,但是我不确定我是如何把这两个任务放在一起的。
NSURLSessionDownloadTaskDelegate似乎拥有下载进度和完成通知所需的所有信息,但我还需要能够停止特定的下载,停止所有的下载,并处理我从下载中返回的数据。

nszi6y05

nszi6y051#

您的直觉是正确的。如果发出许多请求,则NSOperationQueue的值为4或5会非常有用。如果没有这样的值,则如果发出许多请求(例如,50个大图像),则在慢速网络连接下工作时可能会遇到超时问题操作队列也具有其他优点(例如依赖性、分配优先级等),但是控制并发程度是关键的好处,恕我直言。
如果您使用的是基于completionHandler的请求,那么实现基于操作的解决方案就非常简单(这是典型的并发NSOperation子类实现;有关详细信息,请参阅 * 并发编程指南 * 的“操作队列”一章中的 * 配置并发执行的操作 * 部分)。
如果您使用的是基于delegate的实现,那么事情很快就会变得非常棘手,这是因为NSURLSession有一个可以理解(但非常烦人)的特性,即任务级委托是在会话级实现的。两个需要不同处理的不同请求调用共享会话对象上的同一个委托方法。
可以在操作中 Package 基于委托的NSURLSessionTask(我和其他人肯定已经做过了),但它涉及一个笨拙的过程,让会话对象维护一个交叉引用任务标识符和任务操作对象的字典,让它将这些任务委托方法传递给任务对象,然后让任务对象符合各种NSURLSessionTask委托协议,这是一项相当大的工作量,因为NSURLSession在会话上不提供maxConcurrentOperationCount风格的特性(更不用说NSOperationQueue的其他优点了,比如依赖项、完成块等等)。
值得指出的是,基于操作的实现在后台会话中是不太好用的。在应用程序终止后,您的上传/下载任务将继续正常运行(这是一件好事,这是后台请求中相当重要的行为),但当您的应用重新启动时,操作队列和它的所有操作都消失了,所以你必须使用一个纯粹的基于代理的NSURLSession实现来进行后台会话。

pvcm50d1

pvcm50d12#

从概念上讲,NSURLSession是一个操作队列。如果在完成处理程序上恢复NSURLSession任务和断点,堆栈跟踪可能会非常明显。
下面是Ray Wenderlich关于NSURLSession的教程的摘录,其中添加了一条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];

在上面,我们可以看到完成处理程序正在Thread 5 Queue: NSOperationQueue Serial Queue中执行。
因此,我的猜测是每个NSURLSession维护它自己的操作队列,并且添加到会话的每个任务都作为NSOperation执行,因此,维护控制NSURLSession对象或NSURLSession任务的操作队列没有意义。
NSURLSessionTask本身已经提供了等效的方法,如cancelresumesuspend等。
的确,它比您自己的NSOperationQueue具有更少的控制权,但是,NSURLSession是一个新类,其目的无疑是减轻您的负担。

  • 底线:如果你想减少麻烦-但更少的控制-并相信苹果公司代表你执行网络任务胜任,使用NSURLSession。否则,滚动自己的NSURLConnection和你自己的操作队列。*
z0qdvdin

z0qdvdin3#

    • 更新:**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方法内部进行异步调用,并且该方法将在主方法完成后完成**。
我有一些幻灯片是关于如何在苹果操作系统上处理并发的:https://speakerdeck.com/yageek/concurrency-on-darwin.

    • 旧答案**:你可以试试dispatch_group_t。你可以把它们当作GCD的保留计数器。

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

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

bhmjp9jg4#

使用NSURLSession,你不用手动向队列添加任何操作,你可以使用NSURLSession上的- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request方法生成一个数据任务,然后启动(通过调用resume方法)。
您可以提供操作队列,以便控制队列的属性,如果需要,还可以将其用于其他操作。
要对数据任务执行的NSOperation执行的任何常规操作(即启动、暂停、停止、恢复)。
要排队下载50个图像,您只需创建50个NSURLSession将正确排队的数据任务。

20jt8wwn

20jt8wwn5#

如果您正在使用OperationQueue,并且不希望每个操作创建许多并发网络请求,则只需在将每个操作添加到队列后调用queue.waitUntilAllOperationsAreFinished()即可。现在,这些操作将仅在前一个操作完成后执行,从而显著减少并发网络连接的数量。

ar7v8xwq

ar7v8xwq6#

下面是一个将NSOperation与NSURLSession结合使用的示例项目:https://github.com/MacMark/Operations-Demo
对于后台会话,您可以通过在此回调中使用会话的标识符来恢复NSOperation:

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
   // Restore the NSOperation that matches the identifier
   // Let the NSOperation call the completionHandler when it finishes
}
5n0oy7gb

5n0oy7gb7#

也许你正在寻找这个:
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
有点奇怪的是,这不是“内置的”,但是如果你想把NSURL的东西和NSOperation的东西挂起来,看起来你必须在主线程中重用runloop,并使操作成为一个“并发的”操作(“并发的”到队列)。
尽管在您的情况下--如果只是简单的下载,没有后续的、依赖的操作连接--我不确定使用NSOperation会得到什么。

相关问题