处于更高的高度来理解文件下载,它和获取数据有点儿类似。我们的app需要实现以下(NSURLSessionDownloadDelegate)代理方法:
- URLSession:downloadTask:didFinishDownloadingToURL:它会给我们提供在文件下载时存储文件的临时URL,由于代理消息在该方法返回时会被移除,所以我们需要拷贝或者移动该文件到一个新的地址。
- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:给我们提供文件下载进度的状态信息;
- URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:告诉我们在之前下载失败的地方恢复下载。可以根据失败回调方法查看相应的错误
[error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]
- URLSession:task:didCompleteWithError: 告诉app下载失败
对于第一点:在该方法返回之前,它为了读取这个文件必须要打开该文件,或者移动该文件到常驻的位置。当该方法返回之后,如果该文件依然存在于原来的位置,那么该文件将会被删除。
如果在后台session中添加了下载任务,在app进入暂停状态之后,该任务依然会继续执行。如果是在默认的session和短暂的session中添加的下载任务,那么它们在程序重启之后会从头开始执行。
断点下载逻辑
当在服务器数据传输过程中,如果用户要求app暂停下载,我们在程序中可以调用cancelByProducingResumeData:方法来取消该任务。在后面的某个时刻,我们可以把返回的resumedata
(因为cancelByProducingResumeData:
方法的返回变量就叫resumeData
,这里放入原文更好理解,故不进行翻译)传递给downloadTaskWithResumeData:方法 或者downloadTaskWithResumeData:completionHandler:方法中的任意一个去创建一个新的下载任务以继续之前的下载。
断点下载并不是之前的task中,而是新创建了一个任务。
传输失败的情况处理
如果传输失败,代理方法URLSession:task:didCompleteWithError: 会被调用,并传递给我们一个NSError对象。如果这个任务是可以继续的(也就是可以继续下载的),该NSError
对象的userInfo
字典会包含一个以NSURLSessionDownloadTaskResumeData
为key的值,我们可以传递返回的resumeData给downloadTaskWithResumeData:方法或者downloadTaskWithResumeData:completionHandler:方法中的任意一个去创建一个新的下载任务以继续之前的下载。
[error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]
样例
下面展示了下载大文件的例子:
/// Objective-C
NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/ObjC_classic/FoundationObjC.pdf"];
NSURLSessionDownloadTask *downloadTask = [backgroundSession downloadTaskWithURL:url];
[downloadTask resume];
/// 代理
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(@"Session %@ download task %@ wrote an additional %lld bytes (total %lld bytes) out of an expected %lld bytes.\n", session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
NSLog(@"Session %@ download task %@ resumed at offset %lld bytes out of an expected %lld bytes.\n", session, downloadTask, fileOffset, expectedTotalBytes);
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSLog(@"Session %@ download task %@ finished downloading to URL %@\n", session, downloadTask, location);
// Perform the completion handler for the current session
self.completionHandlers[session.configuration.identifier]();
// Open the downloaded file for reading
NSError *readError = nil;
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:location error:readError];
// ...
// Move the file to a new URL
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *cacheDirectory = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] firstObject];
NSError *moveError = nil;
if ([fileManager moveItemAtURL:location toURL:cacheDirectory error:moveError]) {
// ...
}
}
///Swift
if let url = URL(string: "https://www.example.com/") {
let dataTask = defaultSession.dataTask(with: url)
dataTask.resume()
}
/// 代理
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print("Session \(session) download task \(downloadTask) wrote an additional \(bytesWritten) bytes (total \(totalBytesWritten) bytes) out of an expected \(totalBytesExpectedToWrite) bytes.\n")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
print("Session \(session) download task \(downloadTask) resumed at offset \(fileOffset) bytes out of an expected \(expectedTotalBytes) bytes.\n")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Session \(session) download task \(downloadTask) finished downloading to URL \(location)\n")
// Perform the completion handler for the current session
self.completionHandlers[session.configuration.identifier]()
// Open the downloaded file for reading
if let fileHandle = try? FileHandle(forReadingFrom: location) {
// ...
}
// Move the file to a new URL
let fileManager = FileManager.default()
if let cacheDirectory = fileManager.urlsForDirectory(.cachesDirectory, inDomains: .userDomainMask).first {
do {
try fileManager.moveItem(at: location, to: cacheDirectory)
} catch {
// ...
}
}
}