我的应用程序提供了从我们的服务器下载3430高分辨率图像的选项,每个图像大小为50k-600k字节。
最初的方法是只下载所有这些文件-但是我们意识到这给了很多NSURLErrorTimedOut错误,导致程序崩溃。现在,我们已经实现了它,以便我们下载所有图像,但一次下载100张图像。
- (void)batchDownloadImagesFromServer:(BOOL)downloadHiResImages
{
[UIApplication sharedApplication].idleTimerDisabled = YES;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[self generateImageURLList:YES];
[leafletImageLoaderQueue removeAllObjects];
numberOfThumbnailLeft = [uncachedThumbnailArray count];
numberOfHiResImageLeft = [uncachedHiResImageArray count];
NSLog(@"DEBUG: In batchDownloadImagesFromServer numberOfThumbnailLeft %ul , numberOfHiResImageLeft %ul ", numberOfThumbnailLeft, numberOfHiResImageLeft);
numberOfImagesToDownload = numberOfThumbnailLeft;
if (downloadHiResImages)
{
numberOfImagesToDownload += numberOfHiResImageLeft;
}
if (numberTotalToDownload < 0) {
numberTotalToDownload = numberOfHiResImageLeft;
}
int midBatchCt = 0;
// start where we stopped
NSArray *subArray;
NSRange batchRange;
batchRange.location = 0;//uncachedHiResIndex;
NSInteger uncachedNumber = [uncachedHiResImageArray count];
NSLog(@"uncachedHiResIndex and numberTotalToDownload: %d %d", uncachedHiResIndex, numberTotalToDownload);
if (uncachedHiResIndex >= numberTotalToDownload || batchRange.location >= uncachedNumber) {
// we have reached the end of the uncached hires images
NSLog(@" END of download total to download=%ld , uncachedNumber=%ld, # not downloaded is %ld", (long)numberTotalToDownload, uncachedNumber, (long)numberFailedToDownload);
return;
}
if (batchRange.location+100 > uncachedNumber) {
NSInteger imagesUntilEnd = uncachedNumber -1;
batchRange.length = imagesUntilEnd;
NSLog(@"this is uncached number: %d this is uncachedhiresindex:%d and this images until end:%d ", uncachedNumber, uncachedHiResIndex, imagesUntilEnd);
} else {
batchRange.length = 100;
}
NSLog(@" NEW RANGE is from %lul to %lul ", (unsigned long)batchRange.location, batchRange.length);
subArray = [uncachedHiResImageArray subarrayWithRange:batchRange];
if (downloadHiResImages)
{
for ( LeafletURL* aLeafletURL in subArray )
{
LeafletImageLoader* hiResImageLoader = [[LeafletImageLoader alloc] initWithDelegate:self];
[leafletImageLoaderQueue addObject:hiResImageLoader]; // do this before making connection!! //
[hiResImageLoader loadImage:aLeafletURL isThumbnail:NO isBatchDownload:YES];
//// Adding object to array already retains it, so it's safe to release it here. ////
[hiResImageLoader release];
midBatchCt++;
uncachedHiResIndex++;
if (midBatchCt == 10) {
NSLog(@" Waiting for queued images to download...");
NSLog(@" REMOVED from QUEUE %lul , UncachedIndex %lul", numberRemovedFromQueue, uncachedHiResIndex);
break;
}
}
}
if ( [leafletImageLoaderQueue count] == 0 && numberRemovedFromQueue == numberTotalToDownload) {
if([delegate respondsToSelector:@selector(requestDidBatchDownloadImages:)])
{
[delegate requestDidBatchDownloadImages:self];
}
}
}
这已解决了我们的大多数问题。但是,即使在开始批量下载之前,我们也要测试网络连接性。我发现low level ping library给出了准确的往返计时结果。使用GBPing的演示代码作为参考,我在调用batchDownloadImagesFromServer
之前编写了自己的代码来ping服务器。
- (IBAction)preloadAll:(id)sender
{
self.ping = [GBPing new];
self.ping.host = kDefaultDataServer;
self.ping.delegate = self;
self.ping.timeout = 1;
self.ping.pingPeriod = 0.9;
// setup the ping object (this resolves addresses etc)
[self.ping setupWithBlock:^(BOOL success, NSError *error) {
if (success) {
// start pinging
[self.ping startPinging];
// stop it after 5 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"stop it");
[self.ping stop];
self.ping = nil;
});
} else {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Internet Connection"
message:@"Not strong enough internet connection"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* OKButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
[downloadManager batchDownloadImagesFromServer:YES];
}];
[alert addAction:OKButton];
[self presentViewController:alert animated:NO completion:nil];
}
}];
}
我是刚接触网络的人。考虑批处理大小和图像大小,如何确定测试的有效负载大小和超时长度?
答案 0 :(得分:1)
超时长度是每个请求的长度。这只是网络代码在放弃之前等待答复的时候。不应太短,但是对于大多数系统API来说,超时时间大约是一分钟或更长时间,这可能太长了。
此外,请注意,如果连接不良,您仍然会收到超时错误,因此需要修复导致崩溃的原因。您必须能够从超时中恢复。
您没有提供太多有关崩溃的信息(这是什么崩溃?您得到什么回溯?),但是我可以看到可能发生的三件事:
您以严格的循环进行下载,其中没有@autoreleasepool {}
块。这意味着您所有下载的文件数据都累积在RAM中,并耗尽了应用程序的内存限制。确保将自动释放池置于长时间运行的循环中。
您正在主线程上进行这些下载。主线程用于UI和简短操作。如果您的主线程代码执行的操作花费的时间超过几秒钟,那么UIApplication
将无法处理来自用户的触摸事件(以及其他事件),并且操作系统将其无响应地击落。
您正在向服务器发送大量请求,并且服务器内部的某些DDoS保护功能决定在几分钟内忽略来自您的请求,这是一种自我保护的形式。许多服务器在给定的时间内限制了它们将接受来自客户端的请求数量,或者客户端可能同时具有多少个打开的连接。
我认为通过显示执行实际下载的代码会更好。您不必需要来获取准确的ping定时信息,以下载一堆图像文件。
假设上述可能性中的一种或多种是正确的,我建议您像这样实现您的下载代码:
创建所有需要下载的文件URL的列表。
编写代码,以便其顺序下载这些URL。即在上一个文件完成(或失败并且您决定暂时跳过该文件)之前,不要让它开始下载文件。
使用NSURLSession
支持将单个文件下载到文件夹,请勿使用代码获取NSData并自己保存文件。这样,下载完成时无需运行您的应用程序。
请确保您可以判断文件是否已经下载,以防下载中断或在下载中重启手机。您可以例如为此,可以比较它们的名称(如果它们足够独特),或将注释保存到plist中,以使您将下载的文件与文件的来源网址进行匹配,或者构成您情况下的识别特征。
在启动时,检查是否所有文件都在那里。如果不是,请将丢失的文件放在上面的下载列表中,然后按顺序下载它们,如#2所示。
在开始下载任何内容(包括在先前的下载完成或失败之后下载下一个文件)之前,请使用来自Apple SystemConfiguration.framework
的Reachability API进行可达性检查。这将告诉您用户是否完全建立了连接,以及您使用的是WiFi还是蜂窝网络(通常,您不是希望通过大多数蜂窝网络通过蜂窝网络下载大量文件被计量)。
如果图像存储在单独的服务器上,或者它们较小,并且建立连接的开销比实际下载数据要多,则可以修改代码以一次下载多个图像,但是通常情况下,同时从服务器下载4个以上的图像,您可能看不到性能提升,因为每增加一个图像只会减少其他图像的可用带宽。