我如何使用NSURSession的NSOperationQueue?
我正在尝试构build一个批量图像下载器,可以将图像添加到队列中以供下载,我可以找出进度以及下载完成的时间。
通过我的阅读,似乎NSOperationQueue
的队列function和NSURLSession
的networkingfunction似乎是我最好的select,但我很困惑,如何使用这两个串联。
我知道我添加NSOperation
实例到NSOperationQueue
,他们排队。 看来我创build一个NSURLSessionDownloadTask
的下载任务,如果我需要多个任务,多个,但我不知道我怎么把这两个在一起。
NSURLSessionDownloadTaskDelegate
似乎有我需要的下载进度和完成通知的所有信息,但我也需要能够停止特定的下载,停止所有下载,并处理从下载中获得的数据。
你的直觉是正确的。 如果发出很多请求,使用maxConcurrentOperationCount
为4或5的maxConcurrentOperationCount
可能非常有用。 如果没有这个function,如果你发出很多请求(比如50个大图像),那么在慢速networking连接(例如某些蜂窝连接)上工作时,可能会遇到超时问题。 操作队列也有其他的优点(例如依赖关系,分配优先级等),但控制并发的程度是关键的好处,恕我直言。
如果使用的是基于completionHandler
的请求,那么实现基于操作的解决scheme非常简单(这是典型的并发NSOperation
子类的实现;有关更多信息,请参阅“ 并发编程指南”的“ 操作队列”一章中的“为并发执行configuration操作”部分)。
如果你正在使用基于delegate
的实现,事情开始变得相当快,但很快。 这是因为NSURLSession
的一个可理解的(但令人难以置信的令人讨厌的)function,即任务级代表在会话级实现。 (想一想:需要不同处理的两个不同的请求在共享会话对象上调用相同的委托方法。
在一个操作中包装一个基于委托的NSURLSessionTask
是可以做到的(我和其他人确信已经完成了它),但是它涉及到一个难以处理的过程,即让会话对象维护一个字典,用任务操作对象交叉引用任务标识符,让它将这些任务委托方法传递给任务对象,然后让任务对象符合各种NSURLSessionTask
委托协议。 因为NSURLSession
没有在会话中提供maxConcurrentOperationCount
风格的function(对于其他NSOperationQueue
好处,比如依赖关系,完成块等),所以这是相当大量的工作。
值得指出的是,基于操作的实现方式对于后台会话来说有点不起作用。 您的上传/下载任务将在应用程序终止后继续运行(这是一件好事,这在后台请求中是相当重要的行为),但是当您的应用程序重新启动时,操作队列及其所有操作都将消失。 所以你必须为后台会话使用一个纯粹的基于委托的NSURLSession
实现。
从概念上讲,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本身已经提供了等效的方法,如cancel
, resume
, suspend
等等。
确实,控制比你自己的NSOperationQueue更less。 但是再一次,NSURLSession是一个新的类,其目的无疑是减轻你的负担。
底线:如果你想减less麻烦 – 但更less控制 – 并且相信苹果能够以你的名义胜任networking任务,请使用NSURLSession。 否则,使用NSURLConnection和自己的操作队列来滚动自己。
更新: executing
和finishing
属性掌握有关当前NSOperation
状态的知识。 一旦您finishing
设置为YES
并executing
到NO
,您的操作被视为已完成。 处理它的正确方法不需要dispatch_group
,可以简单地写成asynchronousNSOperation
:
- (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
方法asynchronous执行的 。 换句话说: 在main
方法内部进行asynchronous调用,方法结束后方法结束 。
我有一些关于如何处理并发的幻灯片: https : //speakerdeck.com/yageek/concurrency-on-darwin 。
老答案 :你可以试试dispatch_group_t
。 你可以认为他们是GCD的保留柜台。
想象一下NSOperation
子类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; }
使用NSURLSession,您不需要手动将任何操作添加到队列中。 在- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
使用方法- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
来生成一个数据任务,然后启动(通过调用resume方法)。
您可以提供操作队列,以便您可以控制队列的属性,并在需要时将其用于其他操作。
您希望在数据任务上执行的任何常规操作(即启动,暂停,停止,恢复)。
要排队50个图像下载,您可以简单地创buildNSURLSession将正确排队的50个数据任务。
如果您使用的是OperationQueue,并且不希望每个操作都创build多个同时发生的networking请求,则可以在将每个操作添加到队列之后调用queue.waitUntilAllOperationsAreFinished()。 他们现在只能在上一个完成之后才执行,大大减less了同时连接的networking数量。
也许你正在寻找这个:
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
有点奇怪,这不是“内置的”,但是如果你想用NSOperation来连接NSURL的东西,看起来你必须在主线程中重用runloop,并使这个操作成为“并发” '到队列中)。
虽然在你的情况下 – 如果只是简单的下载,没有后续,依赖,操作挂钩 – 我不知道你会得到什么使用NSOperation。