[iOS]网络之NSURLSession的文件下载

少量的数据进行网络传输,根本就满足不了需求。下载文档、音乐、视频等等都需要应用的支持,恰好NSURLSessionDownloadTask能够满足这些需求。

  • NSURLSessionDownloadTask介绍
  • NSURLSessionDownloadDelegate介绍
  • 使用NSURLSessionDownloadTask进行下载
  • 使用NSURLSessionDownloadTask进行断点下载
  • 总结

NSURLSessionDownloadTask介绍

NSURLSessionDownloadTask支持三种任务类型中的下载任务类型,它提供断点下载,因网络原因导致下载没有完成时,它会保留已经下载完的数据,然后返回后ResumeData。API使用者可以利用resumeData进行后续下载。

这三个方法是声明在NSURLSession类里:
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url; 通过URL对象来创建一个下载任务
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;通过NSURLRequest对象来创建一个下载任务
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;通过恢复数据来创建一个下载任务;

这个方法是声明在NSURLSessionDownloadTask类里:
- (void)cancelByProducingResumeData:(void (^)(NSData * _Nullable resumeData))completionHandler; //调用此方法停止下载任务,该方法通过block返回下载未完成的数据信息,可以通过该数据进行断点下载;

//可以发现,下面的信息还是很清晰的,例如下载地址、接收了多少数据、临时文件名等。都是后续断点下载必要的一些信息。
resumeData = /*


<plist version="1.0">
<dict>
    <key>NSURLSessionDownloadURLkey>
    <string>https://opensource.apple.com/tarballs/xnu/xnu-3789.70.16.tar.gzstring>
    <key>NSURLSessionResumeBytesReceivedkey>
    <integer>86594integer>
    <key>NSURLSessionResumeCurrentRequestkey>
    <data>
    YnBsaXN0MDDUAQIDBAUGdXZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
    AAGGoK8QFwcILEdNTlRVVlcrWDlZWmZnaGlqa2xxVSRudWxs3xAfCQoLDA0ODxAREhMU
    FRYXGBkaGxwdHh8gISIjJCUmJygpKSssLS4vMDApLzQrKTY3ODk6OykpPjspL0JDLUVS
    JDFfECBfX25zdXJscmVxdWVzdF9wcm90b19wcm9wX29ial8yMF8QIF9fbnN1cmxyZXF1
    ZXN0X3Byb3RvX3Byb3Bfb2JqXzIxXxAQc3RhcnRUaW1lb3V0VGltZV8QHnJlcXVpcmVz
    U2hvcnRDb25uZWN0aW9uVGltZW91dF8QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bf
    b2JqXzEwViRjbGFzc18QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzExXxAg
    X19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfMTJfECBfX25zdXJscmVxdWVzdF9w
    cm90b19wcm9wX29ial8xM18QGl9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3BzXxAgX19u
    c3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfMTRfECBfX25zdXJscmVxdWVzdF9wcm90
    b19wcm9wX29ial8xNV8QGnBheWxvYWRUcmFuc21pc3Npb25UaW1lb3V0XxAgX19uc3Vy
    bHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfMTZfEBRhbGxvd2VkUHJvdG9jb2xUeXBlc18Q
    IF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzE3XxAgX19uc3VybHJlcXVlc3Rf
    cHJvdG9fcHJvcF9vYmpfMThSJDBfECBfX25zdXJscmVxdWVzdF9wcm90b19wcm9wX29i
    al8xOV8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzlfEB9fX25zdXJscmVx
    dWVzdF9wcm90b19wcm9wX29ial84XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9v
    YmpfN18QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzZfEB9fX25zdXJscmVx
    dWVzdF9wcm90b19wcm9wX29ial81XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9v
    YmpfNF8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzNSJDJfEB9fX25zdXJs
    cmVxdWVzdF9wcm90b19wcm9wX29ial8xXxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
    cF9vYmpfMF8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzIQCYAAgAAjAAAA
    AAAAAAAIgAKAFoAHgAqACoAAgAeAC4AAEACADIANEAKADoAIgACAAIAJgAiAAIAHEBaA
    A4ACgAYI00gPSSlLTFdOUy5iYXNlW05TLnJlbGF0aXZlgACABYAEXxA/aHR0cHM6Ly9v
    cGVuc291cmNlLmFwcGxlLmNvbS90YXJiYWxscy94bnUveG51LTM3ODkuNzAuMTYudGFy
    Lmd60k9QUVJaJGNsYXNzbmFtZVgkY2xhc3Nlc1VOU1VSTKJRU1hOU09iamVjdCNATgAA
    AAAAABAACRAEE///////////U0dFVNNbXA9dYWVXTlMua2V5c1pOUy5vYmplY3Rzo15f
    YIAPgBCAEaNiY2SAEoATgBSAFV8QD0FjY2VwdC1FbmNvZGluZ1ZBY2NlcHRfEA9BY2Nl
    cHQtTGFuZ3VhZ2VdZ3ppcCwgZGVmbGF0ZVMqLypVZW4tdXPST1Btbl8QE05TTXV0YWJs
    ZURpY3Rpb25hcnmjb3BTXxATTlNNdXRhYmxlRGljdGlvbmFyeVxOU0RpY3Rpb25hcnnS
    T1Byc1xOU1VSTFJlcXVlc3SidFNcTlNVUkxSZXF1ZXN0XxAPTlNLZXllZEFyY2hpdmVy
    0Xd4XxAbTlNLZXllZEFyY2hpdmVSb290T2JqZWN0S2V5gAEACAARABoAIwAtADIANwBR
    AFcAmACbAL4A4QD0ARUBOAE/AWIBhQGoAcUB6AILAigCSwJiAoUCqAKrAs4C8AMSAzQD
    VgN4A5oDvAO/A+EEAwQlBCcEKQQrBDQENQQ3BDkEOwQ9BD8EQQRDBEUERwRJBEsETQRP
    BFEEUwRVBFcEWQRbBF0EXwRhBGMEZQRnBGgEbwR3BIMEhQSHBIkEywTQBNsE5ATqBO0E
    9gT/BQEFAgUEBQ0FEQUYBSAFKwUvBTEFMwU1BTkFOwU9BT8FQQVTBVoFbAV6BX4FhAWJ
    BZ8FowW5BcYFywXYBdsF6AX6Bf0GGwAAAAAAAAIBAAAAAAAAAHkAAAAAAAAAAAAAAAAA
    AAYd
    data>
    <key>NSURLSessionResumeEntityTagkey>
    <string>"7edc8d770864146649f38a5d6034f5a5"string>
    <key>NSURLSessionResumeInfoTempFileNamekey>
    <string>CFNetworkDownload_9puQxd.tmpstring>
    <key>NSURLSessionResumeInfoVersionkey>
    <integer>2integer>
    <key>NSURLSessionResumeOriginalRequestkey>
    <data>
    YnBsaXN0MDDUAQIDBAUGUFFYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
    AAGGoKwHCCQ7QUJISUojS0xVJG51bGzfEBkJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAh
    IiMkJSYnKCgqJywjLS4vKionLyonNjclOVIkMV8QEHN0YXJ0VGltZW91dFRpbWVfEB5y
    ZXF1aXJlc1Nob3J0Q29ubmVjdGlvblRpbWVvdXRfECBfX25zdXJscmVxdWVzdF9wcm90
    b19wcm9wX29ial8xMFYkY2xhc3NfECBfX25zdXJscmVxdWVzdF9wcm90b19wcm9wX29i
    al8xMV8QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzEyXxAgX19uc3VybHJl
    cXVlc3RfcHJvdG9fcHJvcF9vYmpfMTNfEBpfX25zdXJscmVxdWVzdF9wcm90b19wcm9w
    c18QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzE0XxAgX19uc3VybHJlcXVl
    c3RfcHJvdG9fcHJvcF9vYmpfMTVfEBpwYXlsb2FkVHJhbnNtaXNzaW9uVGltZW91dF8Q
    FGFsbG93ZWRQcm90b2NvbFR5cGVzUiQwXxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
    cF9vYmpfOV8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzhfEB9fX25zdXJs
    cmVxdWVzdF9wcm90b19wcm9wX29ial83XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
    cF9vYmpfNl8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzVfEB9fX25zdXJs
    cmVxdWVzdF9wcm90b19wcm9wX29ial80XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
    cF9vYmpfM1IkMl8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzFfEB9fX25z
    dXJscmVxdWVzdF9wcm90b19wcm9wX29ial8wXxAfX19uc3VybHJlcXVlc3RfcHJvdG9f
    cHJvcF9vYmpfMhAJIwAAAAAAAAAACIACgAuAB4AJgAmAAIAHgAoQABACgAiAAIAAgAeA
    CIAAgAcQEIADgAKABgjTPA09Kj9AV05TLmJhc2VbTlMucmVsYXRpdmWAAIAFgARfED9o
    dHRwczovL29wZW5zb3VyY2UuYXBwbGUuY29tL3RhcmJhbGxzL3hudS94bnUtMzc4OS43
    MC4xNi50YXIuZ3rSQ0RFRlokY2xhc3NuYW1lWCRjbGFzc2VzVU5TVVJMokVHWE5TT2Jq
    ZWN0I0BOAAAAAAAAEAAJE///////////0kNETU5cTlNVUkxSZXF1ZXN0ok9HXE5TVVJM
    UmVxdWVzdF8QD05TS2V5ZWRBcmNoaXZlctFSU18QG05TS2V5ZWRBcmNoaXZlUm9vdE9i
    amVjdEtleYABAAgAEQAaACMALQAyADcARABKAH8AggCVALYA2QDgAQMBJgFJAWYBiQGs
    AckB4AHjAgUCJwJJAmsCjQKvAtEC1AL2AxgDOgM8A0UDRgNIA0oDTANOA1ADUgNUA1YD
    WANaA1wDXgNgA2IDZANmA2gDagNsA24DcANxA3gDgAOMA44DkAOSA9QD2QPkA+0D8wP2
    A/8ECAQKBAsEFAQZBCYEKQQ2BEgESwRpAAAAAAAAAgEAAAAAAAAAVAAAAAAAAAAAAAAA
    AAAABGs=
    data>
    <key>NSURLSessionResumeServerDownloadDatekey>
    <string>Wed, 06 Sep 2017 22:07:41 GMTstring>
dict>
plist>
*/

NSURLSessionDownloadDelegate介绍

NSURLSessionDownloadDelegate 下载任务的代理对象,任务的进度、数据以及通知都会调用该代理的方法,使用者需实现该代理方法接收这些信息。

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location; //下载完成后,调用此方法,它会提供一个临时文件地址,该地址里的文件是下载内容的存储。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; //提供关于下载进度状态信息。它可以让我们实现合理的提示信息;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes; //下载恢复时调用此方法;如果你想获取用于恢复下载的数据,可以监听“NSURLSessionDownloadTaskResumeData”这个key,来获取ResumeData。如果一个下载发生了错误,它会通过通知传递- userInfo dictionary;

使用NSURLSessionDownloadTask进行下载

//创建会话类型
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:[[downloadTaskDelegate alloc] init] delegateQueue:[NSOperationQueue mainQueue]];

//创建下载任务
NSURLSessionDownloadTask *downloadTask = [defaultSession downloadTaskWithURL:url];
//执行下载任务
[downloadTask resume];

//代理实现 头文件
@interface downloadTaskDelegate : NSObject 
@end
//实现文件
@implementation downloadTaskDelegate

/**
 * NSURLSession 提供一个临时文件地址,供下载内容存储。
 * Note:注意,当此方法返回时,该临时文件会被删除,必须在临时文件删除之前,让该文件永久存储。
 * #param :(NSURLSession *)session 会话类型对象
 * #param :(NSURLSessionDownloadTask *)downloadTask 任务类型对像
 * #param :(NSURL *)location 临时文件的地址
 */
/* Sent when a download task that has completed a download.  The delegate should
 * copy or move the file at the given location to a new location as it will be
 * removed when the delegate message returns. URLSession:task:didCompleteWithError: will
 * still be called.
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{



    //在此方法返回之前,打开临时文件并读取该内容。
    NSError *error = nil;
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:location error:&error];
    //通过NSFileHandle的readDataToEndOfFile方法,我们发现,该临时文件还存在。
    NSLog(@"%@",[[NSString alloc] initWithData:[fileHandle readDataToEndOfFile] encoding:NSUTF8StringEncoding]);
    [fileHandle closeFile];


    /**
     * 保存临时文件,因为此方法返回,临时文件就会被删除
     * 1. 获取文件管理对象
     * 2. 打开Cache目录
     * 3. 移动临时文件到Cache目录
     */
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *cacheDirectory = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] firstObject];
    cacheDirectory = [cacheDirectory URLByAppendingPathComponent:@"localTmp"];
    NSError *moveError = nil;

    //这是为了防止,文件存在会copy错误。所以文件名写死不是应该好方法,应该写成动态的。
    [fileManager removeItemAtURL:cacheDirectory error:&moveError];

    //这里为了演示,临时文件在该方法返回后会被删除,所以是copy临时文件,而不是移动。
    if ([fileManager copyItemAtURL:location toURL:cacheDirectory error:&moveError]) {
        NSLog(@"location:%@",location);
        NSLog(@"%@",cacheDirectory);
    }
    //如果失败,打印错误信息
    NSLog(@"%@",moveError);

}

/** 
 * 提供关于下载进度的状态信息。这个可以帮助我们实现更好的进度条。
 * #param :(NSURLSession *)session 会话类型对象
 * #param :(NSURLSessionDownloadTask *)downloadTask 任务类型对像
 * #param :(int64_t)bytesWritten 每秒下载多少数据 bytes
 * #param :(int64_t)totalBytesWritten 总共写入多少数据 bytes
 * #param :(int64_t)totalBytesExpectedToWrite 期望的数据,也就是下载文件的大小 bytes。
 */
/* Sent periodically to notify the delegate of download progress. */
- (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);
}

/**
 * 下载恢复时调用此方法;如果你想获取用于恢复下载的数据,可以监听“NSURLSessionDownloadTaskResumeData”这个key,来获取ResumeData。
 * #param:(int64_t)fileOffset 上次下载的偏移量,比如上次下载了50个字节中断了,恢复下载的时候,就是从这个偏移量开始。
 * #param :(int64_t)expectedTotalBytes 期望的数据,也就是下载文件的大小 bytes
 *
 */
/* Sent when a download has been resumed. If a download failed with an
 * error, the -userInfo dictionary of the error will contain an
 * NSURLSessionDownloadTaskResumeData key, whose value is the resume
 * data.
 */
- (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);
}

[iOS]网络之NSURLSession的文件下载_第1张图片

注意:下载文件会存储在一个临时文件中,如果不在didFinishDownloadingToURL,进行相应的处理,这个临时文件在该方法执行完毕后被删除

使用NSURLSessionDownloadTask进行断点下载

//创建会话类型
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:[[downloadTaskDelegate alloc] init] delegateQueue:[NSOperationQueue mainQueue]];

//通过resumeData信息来创建断点续传下载任务
NSURLSessionDownloadTask *downloadTask = [defaultSession downloadTaskWithResumeData:resumeData];
//执行下载任务
[downloadTask resume];


/**
 * 下载恢复时调用此方法;如果你想获取用于恢复下载的数据,可以监听“NSURLSessionDownloadTaskResumeData”这个key,来获取ResumeData。
 * #param:(int64_t)fileOffset 上次下载的偏移量,比如上次下载了50个字节中断了,恢复下载的时候,就是从这个偏移量开始。
 * #param :(int64_t)expectedTotalBytes 期望的数据,也就是下载文件的大小 bytes
 *
 */
/* Sent when a download has been resumed. If a download failed with an
 * error, the -userInfo dictionary of the error will contain an
 * NSURLSessionDownloadTaskResumeData key, whose value is the resume
 * data.
 */
- (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);
}

[iOS]网络之NSURLSession的文件下载_第2张图片
[iOS]网络之NSURLSession的文件下载_第3张图片

总结

  1. 进行文件下载的时候,一定要注意保存临时文件,否则临时文件会被删除。
  2. 如果想支持断点续传,要保存ResumData数据,该数据可以通过监听或者实现代理方法进行获得。
  3. 这里用的是默认的会话类型,也可以用后台会话类型创建下载任务。

参考资料

官方文档:URL Session Programming Guide

你可能感兴趣的:(iOS)