【问题标题】:Download huge number of images in the most efficient and fast way以最高效、最快捷的方式下载大量图片
【发布时间】:2016-12-07 05:37:27
【问题描述】:

在我的应用程序中,我需要下载大约 6,000 图像。我知道这很多,但这是必需的。

目前我正在执行以下操作:

NSArray *photos = @[hugeAmountOfPhotoObjects];
for (ZSSPhoto *photo in photos) {
    [self downloadImageWithURL:photo.mobileURL progress:^(double progress) {

    } completion:^(UIImage *image) {

        // Save the image

    } failure:^(NSError *error) {

    }];
}

...

- (void)downloadImageWithURL:(NSURL *)url progress:(void (^)(double progress))progress completion:(void (^)(UIImage *image))completion failure:(void (^)(NSError *error))failure {

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setTimeoutInterval:600];
    self.operationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
    AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
    [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        completion(responseObject);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [self processOperation:operation error:error failure:failure];
    }];
    [requestOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
        double percentDone = (double)totalBytesRead / (double)totalBytesExpectedToRead;
        progress(percentDone);
    }];
    [self.operationQueue addOperation:requestOperation];

}

这里的问题是使用这种方法下载需要很长时间,并且我的一些用户报告由于内存使用率高而崩溃。

有没有更好的方法可以用来下载如此大量的图像文件?

【问题讨论】:

  • 您是否要下载所有照片,然后将其放入数组中,然后仅将其保存到磁盘?
  • @Tj3n - 不,当完成块下载并返回每个图像时,我会将它们保存到磁盘。
  • 我会设置一些机制,一次只下载几个,可能有一个未下载的图像数组,然后是一个 for 循环,将其中的 3 个取出,下载它们,然后在完成提取时还有 3 个等
  • 我希望您不要通过用户的蜂窝连接进行所有这些下载。
  • @rmaddy - 这是用户发起/导入的操作。他们正在导入自己的带有图像链接的文件。

标签: ios objective-c afnetworking-2


【解决方案1】:

你可以递归地尝试一下

NSMutableArray *undownloaded;

- (void) startDownload {

    undownloaded = [photos mutableCopy]; //get a list of the undownloaded images

    for(int i = 0; i < 3;i++) //download 3 at a time
        [self downloadImage];
}


- (void) downloadImage {

    if(undownloaded.count > 0){

        ZSSPhoto *photo = undownloaded.firstObject;
        [undownloaded removeObjectAtIndex:0];

        [self downloadImageWithURL:photo.mobileURL progress:^(double progress) {

        } completion:^(UIImage *image) {

            // Save the image

            [self downloadImage];

        } failure:^(NSError *error) {

            [self downloadImage];
            //[undownloaded addObject:photo]; //insert photo back into the array maybe to retry? warning, could cause infinite loop without some extra logic, maybe the object can keep a fail count itself

        }];
    }
}

警告:未经测试的代码,可能需要一些调整

【讨论】:

  • 见上图,不要把图片解压到内存,直接把数据存到磁盘,避免浪费所有app内存。
【解决方案2】:

速度问题可以通过多线程解决(速度会提高,但可能仍然很慢),同时下载所有图像而不是一次下载一个。但是,内存问题有点复杂。

ARC 将在一切完成后释放所有图像,但在此之前,设备内存中将有 6,000 个图像。您可以优化图像,降低分辨率或逐步下载它们,就像谷歌图片一样(您首先下载将可见的图像,然后当用户向下滚动时,您将图像加载到新的可见区域;下载图像仅在需要时)。

考虑到您下载了足够多的图像会导致内存问题,如果您下载所有图像,您可能会在用户设备中占用大量空间,而“步骤”解决方案也可以解决这个问题。

现在,假设您必须同时下载所有这些图像并且空间不是问题:我想如果您将 downloadImageWithURL:progress: 方法放在并发队列中,图像就会从内存中释放出来保存后(这只是一个假设)。将此添加到您的代码中:

dispatch_queue_t defaultPriorityQueueWithName(const char* name)
{
    dispatch_queue_t dispatchQueue = dispatch_queue_create(name, DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t priorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_set_target_queue(dispatchQueue, priorityQueue);
    return dispatchQueue;
}

并将您的代码更改为:

dispatch_queue_t threadItemLoadImage = defaultPriorityQueueWithName("DownloadingImages");
NSArray *photos = @[hugeAmountOfPhotoObjects];
for (ZSSPhoto *photo in photos) 
{
    dispatch_async(threadItemLoadImage, ^
    {
        [self downloadImageWithURL:photo.mobileURL progress:^(double progress) {

        } completion:^(UIImage *image) {

            // Save the image

        } failure:^(NSError *error) {

        }];
    });
}

您需要删除 setDownloadProgressBlock: 以防它更新某些视图,因为它们将同时下载。另外,警告:totalBytesExpectedToRead 最初并不总是会被正确检索,包含 0,这可能会导致您的应用程序在极少数情况下因除以零而崩溃。在未来的情况下,当您需要使用setDownloadProgressBlock: 时,请在进行除法之前检查totalBytesExpectedToRead 的值。

【讨论】:

  • 你在开玩笑吗?不要下载图像并解压缩到内存中(UIImage)。只需下载文件并保存到磁盘,您就会耗尽应用程序内存。
  • 我只是重复了@Nic Hubbard 代码,添加了多队列的可能性,因为他已经在使用他的downloadImageWithURL:progress: 方法,我不知道他是否有理由使用它,但可以肯定的是,他也可以尝试类似的解决方案来下载文件而不消耗内存(但是,这并不能解决速度问题):stackoverflow.com/questions/4002979/…
  • 您只能通过减小正在下载的图像的大小来降低下载速度。最好的方法是使用减少的色彩空间,然后使用更改进的图像压缩方法来缩小图像大小。可以使用 256 色表来缩小 PNG,但也可以使用其他更新的方法来减小图像大小。
  • 我猜图像的大小并不是真正的速度问题原因。事实是他下载了6000张图片,每次下载的间隔时间也太耗时间了。
猜你喜欢
  • 2012-11-28
  • 2014-11-09
  • 2017-01-19
  • 2015-09-20
  • 1970-01-01
  • 2011-12-19
  • 1970-01-01
  • 1970-01-01
  • 2023-02-26
相关资源
最近更新 更多