Последовательная загрузка нескольких изображений с помощью NSURLSession downloadTask в Objective C - PullRequest
0 голосов
/ 10 сентября 2018

Мое приложение предлагает возможность загрузить с нашего сервера 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 до тех пор, пока не будет завершена загрузка последнего изображения. Нужно ли загружать в фон? Спасибо за предложения!

Ответы [ 2 ]

0 голосов
/ 10 сентября 2018

Это очень похоже на архитектурную проблему. Если вы запускаете загрузки, не ограничивая их, конечно, вы начнете получать тайм-ауты и другие вещи. Подумайте о других приложениях и о том, что они делают. Приложения, которые дают пользователю возможность выполнять несколько загрузок, часто ограничивают возможности их одновременного выполнения. iTunes, например, может поставить в очередь тысячи загрузок, но запускает только 3 одновременно. Ограничение по одному за раз только замедлит работу ваших пользователей. Вам необходим баланс, учитывающий доступную пропускную способность вашего пользователя.

Другая часть этого вопроса - снова подумать, чего хотят ваши пользователи. Каждое из ваших применений хочет каждое изображение? Я не знаю, что вы им предлагаете, но в большинстве приложений, которые получают доступ к таким ресурсам, как изображения или музыка, пользователю решать, что и когда они загрузят. Таким образом, они загружают только то, что им интересно. Поэтому я рекомендую загружать только то, что просматривают пользователи или каким-то образом запросили, что они хотят загрузить.

0 голосов
/ 10 сентября 2018

Мое приложение предлагает возможность загрузить с нашего сервера 3430 изображений с высоким разрешением, каждое из которых имеет размер 50–600 байт.

Это похоже на работу для ресурсов по запросу . Просто превратите эти файлы в ресурсы по требованию, полученные с вашего собственного сервера, и позвольте системе позаботиться о их загрузке в свое приятное время.

...