NSURLSession介绍以及断点下载(完整)

注意点:

#1,文件的保存路径不能是网址!!!
#2,NSOutputStream的作用就是保存网路下载的数据。

1,NSHTTPURLResponse响应对象

1,statusCode:状态码,可以根据这个值判断是否请求出错。
2,allHeaderFields:获得响应体内容
3,URL:一般使用在重定向,如果不需要重定向,响应的url和请求的url是一样的。
4,MIMEType:服务器告诉客户端返回的数据类型
5,textEncodingName :服务器告诉客户端返回内容的编码格式
6,expectedContentLength:服务器返回数据的长度,客户端可以通过该属性获得文件大小
7,suggestedFilename:服务器建议客户端保存文件使用的名字

2,参考:iOS中流(NSStream)的使用

#NSInputStream 和 NSOutputStream
1,NSInputStream 和 NSOutputStream 是NSStream的两个子类,分别对应了读文件和 写文件。
2,NSInputStream 和 NSOutputStream其实是对 CoreFoundation 层对应的CFReadStreamRef 和 CFWriteStreamRef 的高层抽象。

3,断点续传

#1,NSURLSessionTask是一个抽象类,本身不能使用,只能使用它的子类
NSURLSessionDataTask((完美断点)下载)、
NSURLSessionUploadTask(上传)、
NSURLSessionDownloadTask((有缺陷断点)下载)

注意:请求的时候,只要有completionHandler结果回调的,那么都不会走代理方法。只有下载完成之后才会走completionHandler回调。

#2,NSURLSessionDownloadTask实现断点下载(有缺陷)
//如果任务,取消了那么以后就不能恢复了
     //    [self.downloadTask cancel];
     
     //如果采取这种方式来取消任务,那么该方法会通过resumeData保存当前文件的下载信息
     //只要有了这份信息,以后就可以通过这些信息来恢复下载
     [self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) {
     self.resumeData = resumeData;
     }];
     
     -----------
     //继续下载
     //首先通过之前保存的resumeData信息,创建一个下载任务
     self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
     
     [self.downloadTask resume];
     
     局限性:
     
     01 如果用户点击暂停之后退出程序,那么需要把恢复下载的数据写一份到沙盒,代码复杂度增加
     02 如果用户在下载中途未保存恢复下载数据即退出程序,则不具备可操作性
#3,NSURLSessionDownloadTask两种请求方法介绍
//    1,该方法不会默认保存存到沙盒tmp文件中(可以通过NSURLSessionDownloadTask并以代理的方式来完成大文件的下载,但是需要手动存到沙盒中)
        NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:url];

//    2,内部默认已经实现了边下载边写入沙盒tmp文件中操作,所以不用开发人员担心内存问题
    //注意,tmp中存储的后缀是.tmp,可以通过剪切文件来修改后缀。
    //    缺点:不能监听下载的进度。
//    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {}];

4,任务的三种操作

    #启动task:
    [dataTask resume];
    #取消task
    [dataTask cancel];
    #暂停task
    [dataTask suspend];

5,实现断点下载最好的方式是使用NSURLSessionDataTask实现大文件离线断点下载

#流程:
文件是否下载完成-->文件下载中还是暂停下载-->是否存在文件下载的目录-->创建流,用NSURLSessionDataTask实现断点下载(如下)
#注意:一个下载文件URL需要两部分:task和stream
#HSFileName(url):表示通过URL加密后对应的文件名
#通过taskIdentifier获取task的时候,需要强转一下
//根据url获得对应的下载任务
- (NSURLSessionDataTask *)getTask:(NSString *)url
{
    return (NSURLSessionDataTask *)[self.tasks valueForKey:HSFileName(url)];
}
#正文:
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    // 创建流
    NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:HSFileFullpath(url) append:YES];
    
    // 创建请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
    
    // 设置请求头(从上次下载的长度开始继续下载)
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-", HSDownloadLength(url)];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    // 创建一个Data任务(若之前已经下载过一部分的文件了,那么这里是从上次下载到的位置开始下载的,这里创建的task就会赋值上新的taskIdentifier,并且把原来URL对应的task替换掉。)
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    NSUInteger taskIdentifier = arc4random() % ((arc4random() % 10000 + arc4random() % 10000));
    [task setValue:@(taskIdentifier) forKeyPath:@"taskIdentifier"];

    // 保存任务
    [self.tasks setValue:task forKey:HSFileName(url)];

    HSSessionModel *sessionModel = [[HSSessionModel alloc] init];
    sessionModel.url = url;
    sessionModel.progressBlock = progressBlock;
    sessionModel.stateBlock = stateBlock;
    sessionModel.stream = stream;
    [self.sessionModels setValue:sessionModel forKey:@(task.taskIdentifier).stringValue];
    //开始下载(然后就到代理方法中进行操作即可)
    [self start:url];

#pragma mark - 代理
#pragma mark NSURLSessionDataDelegate
/**
 * 接收到响应(进行打开下载任务对应的流,然后保存该文件的总大小到本地。)
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    
    HSSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier];
    
    // 打开流
    [sessionModel.stream open];
    
    // 获得服务器这次请求 返回数据的总长度(Content-Length对应的是当前下载的URL对应的文件大小,也许文件已经下载过一部了,现在是继续下载的,所以这里后面又加上了已经下载的文件的大小)
    NSInteger totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + HSDownloadLength(sessionModel.url);
    sessionModel.totalLength = totalLength;
    
    //获取plist文件 (存储总长度到plist文件中,HSTotalLengthFullpath:plist文件的路径)
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:HSTotalLengthFullpath];
    if (dict == nil) dict = [NSMutableDictionary dictionary];
    dict[HSFileName(sessionModel.url)] = @(totalLength);
    [dict writeToFile:HSTotalLengthFullpath atomically:YES];
    
    // 接收这个请求,允许接收服务器的数据。(因为系统默认是不响应下载任务的)
    completionHandler(NSURLSessionResponseAllow);
}

/**
 * 接收到服务器返回的数据
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    HSSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier];
    
    // 写入数据
    [sessionModel.stream write:data.bytes maxLength:data.length];
    
    // 下载进度
    NSUInteger receivedSize = HSDownloadLength(sessionModel.url); //已经下载的文件的大小
    NSUInteger expectedSize = sessionModel.totalLength; //文件的总大小
    CGFloat progress = 1.0 * receivedSize / expectedSize;

    sessionModel.progressBlock(receivedSize, expectedSize, progress);
}

/**
 * 请求完毕(成功|失败)
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    HSSessionModel *sessionModel = [self getSessionModel:task.taskIdentifier];
    if (!sessionModel) return;
    
    if ([self isCompletion:sessionModel.url]) {
        // 下载完成
        sessionModel.stateBlock(DownloadStateCompleted);
    } else if (error){
        // 下载失败
        sessionModel.stateBlock(DownloadStateFailed);
    }
    
    // 关闭流
    [sessionModel.stream close];
    sessionModel.stream = nil;
    
    // 清除任务
    [self.tasks removeObjectForKey:HSFileName(sessionModel.url)];
    [self.sessionModels removeObjectForKey:@(task.taskIdentifier).stringValue];
}

1,本文参考链接
2,NSURLSession上传文件

你可能感兴趣的:(NSURLSession介绍以及断点下载(完整))