我的应用程序提供从我们的服务器下载3430高分辨率图像的选项,每个图像大小为50k - 600k字节。
最初的方法是只下载所有这些 - 但我们意识到这给了很多NSURLErrorTimedOut错误并且崩溃了我们的程序。然后我们实现它,以便我们下载所有图像,但一次批量处理100个图像。 Someone on SO建议我们实际下载这样的下载:
创建需要下载的所有文件URL的列表。
编写代码,以便按顺序下载这些URL。即不要让它开始下载文件,直到前一个文件完成(或失败,你决定暂时跳过它)。
使用NSURLSession支持将单个文件下载到文件夹,不要使用代码获取NSData并自行保存文件。这样,下载完成后,您的应用程序不需要运行。
确保您可以判断文件是否已经下载,以防下载中断,或者在下载过程中重新启动手机。你可以,例如通过比较它们的名称(如果它们足够独特),或者将注释保存到plist,以便将下载的文件与其来源的URL匹配,或者在您的情况下构成识别特征的任何内容,来执行此操作。
在启动时,检查是否所有文件都在那里。如果没有,请将缺失的内容放在上面的下载列表中,然后按顺序下载,如#2。
在开始下载任何内容之前(包括在上一次下载完成或失败后下载下一个文件),使用Apple的SystemConfiguration.framework中的Reachability API进行可访问性检查。这将告诉您用户是否完全连接,以及您是使用WiFi还是蜂窝电话(通常,您不希望通过蜂窝网络下载大量文件,大多数蜂窝连接都是计量的)。
我们在此处创建要下载的所有图像的列表:
- (void)generateImageURLList:(BOOL)batchDownloadImagesFromServer
{
NSError* error;
NSFetchRequest* leafletURLRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription* leafletURLDescription = [NSEntityDescription entityForName:@"LeafletURL" inManagedObjectContext:managedObjectContext];
[leafletURLRequest setEntity:leafletURLDescription];
numberOfImages = [managedObjectContext countForFetchRequest:leafletURLRequest error:&error];
NSPredicate* thumbnailPredicate = [NSPredicate predicateWithFormat:@"thumbnailLocation like %@", kLocationServer];
[leafletURLRequest setPredicate:thumbnailPredicate];
self.uncachedThumbnailArray = [managedObjectContext executeFetchRequest:leafletURLRequest error:&error];
NSPredicate* hiResPredicate = [NSPredicate predicateWithFormat:@"hiResImageLocation != %@", kLocationCache];
[leafletURLRequest setPredicate:hiResPredicate];
self.uncachedHiResImageArray = [managedObjectContext executeFetchRequest:leafletURLRequest error:&error];
}
我们使用NSURLSession通过调用hitServerForUrl
并实现didFinishDownloadingToURL
将单个图像下载到文件夹:
- (void)hitServerForUrl:(NSURL*)requestUrl {
NSURLSessionConfiguration *defaultConfigurationObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigurationObject delegate:self delegateQueue: nil];
NSURLSessionDownloadTask *fileDownloadTask = [defaultSession downloadTaskWithURL:requestUrl];
[fileDownloadTask resume];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
if (isThumbnail)
{
leafletURL.thumbnailLocation = kLocationCache;
}
else
{
leafletURL.hiResImageLocation = kLocationCache;
}
// Filename to write to
NSString* filePath = [leafletURL pathForImageAtLocation:kLocationCache isThumbnail:isThumbnail isRetina:NO];
// If it's a retina image, append the "@2x"
if (isRetina_) {
filePath = [filePath stringByReplacingOccurrencesOfString:@".jpg" withString:@"@2x.jpg"];
}
NSString* dir = [filePath stringByDeletingLastPathComponent];
[managedObjectContext save:nil];
NSError* error;
[[NSFileManager defaultManager] createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:&error];
NSURL *documentURL = [NSURL fileURLWithPath:filePath];
NSLog(@"file path : %@", filePath);
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
//Remove the old file from directory
}
[[NSFileManager defaultManager] moveItemAtURL:location
toURL:documentURL
error:&error];
if (error){
//Handle error here
}
}
这段代码调用loadImage
,它调用`hitServer:
-(void)downloadImagesFromServer{
[self generateImageURLList:NO];
[leafletImageLoaderQueue removeAllObjects];
numberOfHiResImageLeft = [uncachedHiResImageArray count];
for ( LeafletURL* aLeafletURL in uncachedHiResImageArray)
{
//// Do the same thing again, except set isThumb = NO. ////
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];
uncachedHiResIndex++;
NSLog(@"uncached hi res index: %ld, un cached hi res image array size: %lu", (long)uncachedHiResIndex, (unsigned long)[uncachedHiResImageArray count]);
}
}
- (void)loadImage:(LeafletURL*)leafletURLInput isThumbnail:(BOOL)isThumbnailInput isBatchDownload:(BOOL)isBatchDownload isRetina:(BOOL)isRetina
{
isRetina_ = isRetina;
if (mConnection)
{
[mConnection cancel];
[mConnection release];
mConnection = nil;
}
if (mImageData)
{
[mImageData release];
mImageData = nil;
}
self.leafletURL = leafletURLInput;
self.isThumbnail = isThumbnailInput;
NSString* location = (self.isThumbnail) ?leafletURL.thumbnailLocation :leafletURL.hiResImageLocation;
//// Check if the image needs to be downloaded from server. If it is a batch download, then override the local resources////
if ( ([location isEqualToString:kLocationServer] || (isBatchDownload && [location isEqualToString:kLocationResource])) && self.leafletURL.rawURL != nil )
{
//NSLog(@"final loadimage called server");
//// tell the delegate to get ride of the old image while waiting. ////
if([delegate respondsToSelector:@selector(leafletImageLoaderWillBeginLoadingImage:)])
{
[delegate leafletImageLoaderWillBeginLoadingImage:self];
}
mImageData = [[NSMutableData alloc] init];
NSURL* url = [NSURL URLWithString:[leafletURL pathForImageOnServerUsingThumbnail:self.isThumbnail isRetina:isRetina]];
[self hitServerForUrl:url];
}
//// if not, tell the delegate that the image is already cached. ////
else
{
if([delegate respondsToSelector:@selector(leafletImageLoaderDidFinishLoadingImage:)])
{
[delegate leafletImageLoaderDidFinishLoadingImage:self];
}
}
}
目前,我正在试图弄清楚如何按顺序下载图像,这样我们就不会在最后一张图像下载完成之前调用hitServer
。我需要在后台下载吗?谢谢你的建议!
我的应用程序提供从我们的服务器下载3430高分辨率图像的选项,每个图像大小为50k - 600k字节。
这似乎是on-demand resources的工作。只需将这些文件转换为从您自己的服务器获得的按需资源,让系统在自己的甜蜜时间内处理下载。
这听起来非常像一个建筑问题。如果你在没有限制它们的情况下发射下载,你当然会开始获得超时和其他东西。考虑其他应用程序及其功能。使用户能够进行多次下载的应用程序通常会限制一次可能发生的情况。例如,iTunes可以排队数千次下载,但一次只能运行3次。一次限制一个只会减慢用户的速度。您需要一个考虑用户可用带宽的余额。
另一部分是再次考虑用户的需求。您的每一个用途都需要每个图像吗?我不知道你提供的是什么,但在大多数访问图像或音乐等资源的应用程序中,由用户下载的内容和时间取决于用户。因此,他们只下载他们感兴趣的内容。所以我建议只下载用户正在查看的内容或以某种方式请求他们下载。