Мое приложение предлагает возможность загрузить с нашего сервера 3430 изображений с высоким разрешением, каждое изображение размером 50–600 байт.
Первоначальный подход состоял в том, чтобы просто загрузить все из них - но мы поняли, что это дало много ошибок NSURLErrorTimedOut и привело к сбою нашей программы. Затем мы реализовали его таким образом, что загружаем все изображения, но партиями по 100 изображений одновременно. Кто-то на SO предположил, что мы действительно реализуем нашу загрузку так:
Создайте список всех URL файлов, которые необходимо загрузить.
Напишите ваш код, чтобы он загружал эти URL-адреса последовательно. То есть делать
не позволяйте начать загрузку файла, пока предыдущий
закончено (или не удалось, и вы решили пропустить это сейчас).
Используйте поддержку NSURLSession для загрузки отдельного файла в
папку, не используйте код, чтобы получить NSData и сохранить файл
сам. Таким образом, ваше приложение не должно быть запущено, пока
загрузка заканчивается.
Убедитесь, что вы можете сказать, был ли файл уже загружен или
нет, в случае, если ваша загрузка прерывается или телефон перезагружается
в середине загрузки. Вы можете, например, сделать это путем сравнения их имен (если
они достаточно уникальны) или сохраняют заметку в листе, который позволяет
сопоставить загруженный файл с URL-адресом, из которого он получен, или любым другим
является идентифицирующей характеристикой в вашем случае.
При запуске проверьте, все ли файлы есть. Если нет, поставьте недостающие
те, что в списке загрузки выше, и загружайте их последовательно, как в # 2.
Прежде чем вы начнете что-либо скачивать (включая загрузку
следующий файл после завершения или сбоя предыдущей загрузки), выполните
проверка достижимости с использованием Reachability API от Apple
SystemConfiguration.framework. Это скажет вам, есть ли у пользователя
соединение вообще, и будь вы по 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
до тех пор, пока не будет завершена загрузка последнего изображения. Нужно ли загружать в фон? Спасибо за предложения!