NSOperation和NSURLConnection神秘

我试图从一些服务器使用NSOperation和NSOperationQueue下载多个图像。 我的主要问题是下面的代码片段和这个链接有什么区别,性能明智吗? 我更喜欢第二种解决scheme,因为我们对操作有更多的控制,它更加清洁,如果连接失败,可以正确处理。

如果我尝试使用下面的代码从服务器上下载大约300张图片,我的应用程序将会有相当大的延迟,如果我启动应用程序,并立即进入主屏幕,然后回到应用程序,我会崩溃,因为没有足够的时间让应用程序再次活跃。 如果我取消注释[queue setMaxConcurrentOperationCount:1],则用户界面是响应的,如果它进入后台并返回到前台,它将不会崩溃。

但是,如果我实现类似于上面的链接,我不需要担心设置maxConcurrentOperationCount,默认值是罚款。 一切都响应,没有崩溃,似乎所有的队列得到更快完成。

所以这带来了我的第二个问题,为什么[queue setMaxConcurrentOperationCount:1]在我的代码中有如此大的影响? 从文档中,我认为将maxConcurrentOperationCount保留为默认值是可以的,而这只是告诉队列根据某些因素决定最佳值。

这是我在堆栈溢出的第一篇文章,所以希望这是有道理的,谢谢你的帮助!

NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //[queue setMaxConcurrentOperationCount:1]; for(NSURL *URL in URLArray) { [queue addOperationWithBlock:^{ NSHTTPURLResponse *response = nil; NSError *error = nil; NSData * data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:&response error:&error]; if(!error && data) { [data writeToFile:path atomically:YES]; } }]; } 

我将以相反的顺序解决这些问题。 你问:

所以这带来了我的第二个问题,为什么[queue setMaxConcurrentOperationCount:1]在我的代码中有如此大的影响? 从文档中,我认为将maxConcurrentOperationCount保留为默认值是可以的,而这只是告诉队列根据某些因素决定最佳值。

使用NSURLConnection ,您不能有超过四个或五个连接同时下载。 因此,如果你没有设置maxConcurrentOperationCount ,那么操作队列并不知道你正在处理NSURLConnection ,因此当你将300个NSOperation对象添加到你的队列时,队列会尝试启动一个非常多的NSOperation对象(64-我认为)同时。 但是由于只有4或5个NSURLConnection请求可以同时运行,其余的由队列启动的请求将等待四个或五个可能的连接中的一个可用,并且有这么多的下载请求,很可能其中很多会超时失败。

通过使用1的maxConcurrentOperationCount ,这适用于一个相当严厉的解决scheme,只能一次运行一个。 我build议一个妥协,即maxConcurrentOperationCount为4,它享有一定程度的并发性(和巨大的性能增益),但不是那么多,我们冒着连接超时和失败的风险。

回到Dave Drubin的NSOperation ,他对包含在一个操作中的synchronousRequest有了很大的改进。 话虽如此,他忽略了解决并发请求的一个基本特征,即取消。 你应该包括一个检查,看看操作是否被取消,如果是的话,取消连接:

 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { if ([self isCancelled]) { [connection cancel]; [self finish]; return; } [_data appendData:data]; } 

同样,他也应该在start方法中这样做。

 - (void)start { // The Apple docs say "Always check for cancellation before launching the task." if ([self isCancelled]) { [self willChangeValueForKey:@"isFinished"]; _isFinished = YES; [self didChangeValueForKey:@"isFinished"]; return; } if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } NSLog(@"opeartion for <%@> started.", _url); [self willChangeValueForKey:@"isExecuting"]; _isExecuting = YES; [self didChangeValueForKey:@"isExecuting"]; NSURLRequest * request = [NSURLRequest requestWithURL:_url]; _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; if (_connection == nil) [self finish]; } 

我可能会build议对Dave的其他样式进行改进,但是这些都是次要的,我认为他能够获得大部分的重要信息。 没有检查取消是唯一明显的大问题,跳出我。

无论如何,有关并发操作的讨论,请参阅“ 并发编程指南”的 “为并发执行configuration操作”部分。

此外,当testing这些巨大的下载,我鼓励你压力testing你的应用程序与networking链接调节器(可用于Mac /模拟器下载“硬件IO工具”下的“Xcode” – “开发开发工具“ – ”更多开发者工具“;如果您启用iOS设备进行开发,在”设置“应用程序的”常规“ – ”开发人员“下也会有一个networking链接调节器设置。 当我们在我们的开发环境高度优化的情况下testing我们的应用程序时,很多这些与超时有关的问题不会显现出来。 使用networking链路调节器来模拟不太理想的真实世界的场景是非常重要的。

我更喜欢第二种解决scheme,因为我们对操作有更多的控制

如果你提到你自己的解决scheme,那么实际情况恰恰相反:

由于同步便捷方法sendSynchronousRequest:你基本上没有办法完成更多的实际需求,比如authentication,更好的error handling和定制的数据处理,以及任何非演示或玩具应用程序通常需要的其他function。

然而令人失望的是缺乏取消操作的能力。 一旦启动,您将无法取消阻止操作。 目前还不清楚如何取消“for循环”。 所以,一旦循环开始,你不能阻止它。

你可能想要在networking和SO上search更复杂(更现代)的方法。

当队列的MaxConcurrentOperationCount大于1时,通过完成操作就可以locking和解锁队列,尽pipe如此,当您将更多的作业添加到队列中时,队列仍处于locking和解锁状态。但是,如果将队列设置为1,作业开始工作,这意味着URLArray的循环更快结束(避免“不断死锁”)。

如果我正确地记得NSOperationQueue api,你可以“暂停”和“恢复”它…我build议你把它设置为“暂停”,直到你完成所有的工作,然后设置它恢复