NSOperation和NSURLConnection神秘化了

时间:2014-03-07 20:21:22

标签: ios iphone nsurlconnection nsoperation nsoperationqueue

我正在尝试使用NSOperation和NSOperationQueue从某些服务器下载多个图像。我的主要问题是下面的代码片段与此链接http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/之间的区别是什么?我更喜欢第二种解决方案,因为我们对操作有更多的控制,它更清洁,如果连接失败,你可以正确处理它。

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

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

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

这是我关于Stack Overflow的第一篇文章,所以希望这是有道理的,感谢您的帮助!

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

3 个答案:

答案 0 :(得分:5)

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

  

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

使用NSURLConnection,您不能同时下载超过四个或五个连接。因此,如果您未设置maxConcurrentOperationCount,则操作队列不知道您正在处理NSURLConnection,因此当您向队列中添加300个NSOperation个对象时,队列将尝试同时启动大量的(64-ish,我认为)。但由于只有4个或5个NSURLConnection请求可以同时运行,所以队列启动的其余请求将等到四个或五个可能的连接中的一个可用,并且有如此多的下载请求,很可能他们中的许多人会超时而失败。

使用maxConcurrentOperationCount为1,对此问题采用相当严厉的解决方案,一次只运行一个。我建议妥协,即4 maxConcurrentOperationCount,它具有一定程度的并发性(以及巨大的性能提升),但不是很多,以至于我们冒着连接超时和失败的风险。

回到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];
}

我可能会建议对Dave的例子进行其他风格上的改进,但这些都是次要的东西,而且我认为他获得了大部分的大图片。未能检查取消是唯一一个突然出现在我身上的大问题。

无论如何,有关并发操作的讨论,请参阅Concurrency Programming Guide配置并发执行操作部分。

此外,在测试这些大量下载时,我建议您使用网络链接调节器(可在Mac上使用“Xcode”下的“硬件IO工具”下载的Mac /模拟器进行压力测试) - “Open Developer Tool” - “更多开发人员工具”;如果您启用iOS设备进行开发,则在“常规” - “设置”应用中的“开发人员”下也会设置网络链接调整设置。当我们在高度优化的开发环境场景中测试应用程序时,很多这些与超时相关的问题都无法体现出来。使用网络链路调节器模拟不太理想的真实场景非常重要。

答案 1 :(得分:1)

  

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

如果您参考自己的解决方案,那么事实恰恰相反:

由于同步便捷方法sendSynchronousRequest:,您基本上无法完成更多实际要求,例如身份验证,更好的错误处理和自定义数据处理,以及任何非应用程序中通常需要的许多其他功能演示或玩具应用程序。

然而,糟糕的是缺乏取消操作的能力。一旦启动,您就无法取消阻止操作。而且还不清楚你想如何取消“for循环”。因此,一旦循环开始,你就无法阻止它。

您可能希望在网络和SO上搜索更复杂(更现代)的方法。

答案 2 :(得分:-1)

队列的MaxConcurrentOperationCount是&gt; 1当您仍在向队列中添加更多作业时,队列很可能会被完成的操作锁定和解锁,但是当您将其设置为1时,队列会在作业开始工作之前完全填满,意味着URLArray的循环结束得更快(避免不断地“死锁”)。

如果我正确地回忆起NSOperationQueue api,你可以“暂停”并“恢复”它...我建议你将它设置为“暂停”,直到你完成添加所有作业,然后将其设置为恢复< / p>

相关问题