首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在iOS中批量下载多个文件

在iOS中批量下载多个文件
EN

Stack Overflow用户
提问于 2012-12-22 04:12:46
回答 6查看 23.2K关注 0票数 25

我知道我可以使用GCD来进行异步下载,但是我该如何批量下载大约10个文件呢?

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2012-12-22 09:05:13

这个答案现在已经过时了。既然NSURLConnection已被弃用,而NSURLSession现在可用,这就为下载一系列文件提供了更好的机制,避免了这里所设想的解决方案的大部分复杂性。请参阅讨论NSURLSession的我的other answer

出于历史的目的,我将把这个答案保留在下面。

我相信有很多很好的解决方案,但是我写了一个小downloader manager来处理这个场景,你想要下载一堆文件。只需将单个下载添加到下载管理器中,当一个下载完成时,它将启动下一个排队的下载。您可以指定希望它并发执行的次数(我默认为4次),因此不需要进行批处理。如果没有其他事情,这可能会激起一些关于如何在您自己的实现中做到这一点的想法。

请注意,这提供了两个优点:

  1. 如果你的文件很大,这永远不会在内存中保存整个文件,而是在下载时将其流式传输到持久存储中。这将显著减少下载过程的内存占用。
  2. 在下载文件时,会使用委派协议通知您下载的进度。

我已经尝试在Download Manager github page的主页上描述了相关的类和正确的操作。

我要说的是,这是为了解决一个特殊的问题而设计的,在这个问题中,我想要跟踪正在下载的大文件的下载进度,并且我不想一次将整个文件保存在内存中(例如,如果你正在下载一个100mb的文件,你真的想在下载时将其保存在RAM中吗?)

虽然我的解决方案解决了这些问题,但如果您不需要,使用操作队列的解决方案要简单得多。事实上,你甚至暗示了这种可能性:

我知道我可以使用GCD进行异步下载,但是我该如何批量下载大约10个文件呢?..。

我不得不说,异步下载给我的印象是正确的解决方案,而不是试图通过批量下载来缓解下载性能问题。

您谈到了使用GCD队列。就我个人而言,我只是创建了一个操作队列,这样我就可以指定我想要多少个并发操作,并使用NSData方法dataWithContentsOfURLwriteToFile:atomically:下载单个文件,使每个下载都有自己的操作。

因此,例如,假设您有一组要下载的文件的URL,它可能是:

代码语言:javascript
运行
复制
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;

for (NSURL* url in urlArray)
{
    [queue addOperationWithBlock:^{
        NSData *data = [NSData dataWithContentsOfURL:url];
        NSString *filename = [documentsPath stringByAppendingString:[url lastPathComponent]];
        [data writeToFile:filename atomically:YES];
    }];
}

很好很简单。通过设置queue.maxConcurrentOperationCount,你可以享受并发,同时不会因为太多的并发请求而使你的应用(或服务器)崩溃。

如果您需要在操作完成时收到通知,您可以这样做:

代码语言:javascript
运行
复制
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;

NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self methodToCallOnCompletion];
    }];
}];

for (NSURL* url in urlArray)
{
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSData *data = [NSData dataWithContentsOfURL:url];
        NSString *filename = [documentsPath stringByAppendingString:[url lastPathComponent]];
        [data writeToFile:filename atomically:YES];
    }];
    [completionOperation addDependency:operation];
}

[queue addOperations:completionOperation.dependencies waitUntilFinished:NO];
[queue addOperation:completionOperation];

这将做同样的事情,除了它将在所有下载完成时调用主队列上的methodToCallOnCompletion

票数 47
EN

Stack Overflow用户

发布于 2013-12-28 06:53:12

顺便说一句,iOS 7(和MacOS10.9)提供了URLSessionURLSessionDownloadTask,它们很好地处理了这个问题。如果你只想下载一堆文件,你可以这样做:

代码语言:javascript
运行
复制
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession              *session       = [NSURLSession sessionWithConfiguration:configuration];

NSString      *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSFileManager *fileManager   = [NSFileManager defaultManager];

for (NSString *filename in self.filenames) {
    NSURL *url = [baseURL URLByAppendingPathComponent:filename];
    NSURLSessionTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        NSString *finalPath = [documentsPath stringByAppendingPathComponent:filename];

        BOOL success;
        NSError *fileManagerError;
        if ([fileManager fileExistsAtPath:finalPath]) {
            success = [fileManager removeItemAtPath:finalPath error:&fileManagerError];
            NSAssert(success, @"removeItemAtPath error: %@", fileManagerError);
        }

        success = [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:finalPath] error:&fileManagerError];
        NSAssert(success, @"moveItemAtURL error: %@", fileManagerError);

        NSLog(@"finished %@", filename);
    }];
    [downloadTask resume];
}

也许,考虑到你的下载花费了“大量的时间”,你可能希望他们即使在应用程序进入后台后也能继续下载。如果是这样的话,您可以使用backgroundSessionConfiguration而不是defaultSessionConfiguration (尽管您必须实现NSURLSessionDownloadDelegate方法,而不是使用completionHandler块)。这些后台会话速度较慢,但是,即使用户离开了你的应用程序,它们也会发生。因此:

代码语言:javascript
运行
复制
- (void)startBackgroundDownloadsForBaseURL:(NSURL *)baseURL {
    NSURLSession *session = [self backgroundSession];

    for (NSString *filename in self.filenames) {
        NSURL *url = [baseURL URLByAppendingPathComponent:filename];
        NSURLSessionTask *downloadTask = [session downloadTaskWithURL:url];
        [downloadTask resume];
    }
}

- (NSURLSession *)backgroundSession {
    static NSURLSession *session = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundId];
        session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    });

    return session;
}

#pragma mark - NSURLSessionDownloadDelegate

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSString *documentsPath    = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *finalPath        = [documentsPath stringByAppendingPathComponent:[[[downloadTask originalRequest] URL] lastPathComponent]];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL success;
    NSError *error;
    if ([fileManager fileExistsAtPath:finalPath]) {
        success = [fileManager removeItemAtPath:finalPath error:&error];
        NSAssert(success, @"removeItemAtPath error: %@", error);
    }

    success = [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:finalPath] error:&error];
    NSAssert(success, @"moveItemAtURL error: %@", error);
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    // Update your UI if you want to
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    // Update your UI if you want to
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (error)
        NSLog(@"%s: %@", __FUNCTION__, error);
}

#pragma mark - NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
    NSLog(@"%s: %@", __FUNCTION__, error);
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    AppDelegate *appDelegate = (id)[[UIApplication sharedApplication] delegate];
    if (appDelegate.backgroundSessionCompletionHandler) {
        dispatch_async(dispatch_get_main_queue(), ^{
            appDelegate.backgroundSessionCompletionHandler();
            appDelegate.backgroundSessionCompletionHandler = nil;
        });
    }
}

顺便说一句,这里假设你的应用委托有一个backgroundSessionCompletionHandler属性:

代码语言:javascript
运行
复制
@property (copy) void (^backgroundSessionCompletionHandler)();

如果应用程序被唤醒以处理URLSession事件,则应用程序委托将设置该属性:

代码语言:javascript
运行
复制
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
    self.backgroundSessionCompletionHandler = completionHandler;
}

有关背景NSURLSession的苹果演示,请参阅Simple Background Transfer示例。

票数 20
EN

Stack Overflow用户

发布于 2012-12-22 04:40:34

如果所有PDF都来自您控制的服务器,那么一种选择是让单个请求传递您想要的文件列表(作为URL上的查询参数)。然后,您的服务器可以将请求的文件压缩到单个文件中。

这将减少您需要发出的单个网络请求的数量。当然,您需要更新您的服务器来处理此类请求,并且您的应用程序需要解压缩返回的文件。但这比发出大量单独的网络请求要高效得多。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13996621

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档