平时项目开发中,经常遇到下载视频、语音、图片等等,其中断点续传是最常见的,当然这也是根据产品需求而定的,如果文件很小,就用不到断点,嗖地一下就下载好了。

断点续传可以用苹果原生的方法,也可以用AFNetworking。
本节先讲苹果原生的文件下载方法,这里需要了解NSURLSession:

一、NSURLSession简介

NSURLConnection在iOS9被宣布弃用,NSURLSession是苹果在iOS7后为HTTP数据传输提供的一系列接口,比NSURLConnection更强大,更好用。其实不难,今天从使用的角度介绍下。

1.使用NSURLSession,共分两步:

第一步 通过NSURLSession的实例创建task
第二部 执行task

2.NSURLSessionTask

NSURLSessionTask可以简单理解为任务:如数据请求任务、下载任务、上传任务等等。NSURLSessionTask是一个抽象类,其下面有三个子类:NSURLSessionDataTask
NSURLSessionUploadTask
NSURLSessionDownloadTask

从这几个子类的名字就可以看出他们的作用了。接下来我们就从不同类型的任务出发,来使用session:

3.NSURLSessionDataTask

这个类是和数据相关的任务,但其实dataTask完全可以胜任downloadTask和uploadTask的工作。这可能也是我们使用最多的task种类,而且下面要讲的的断点续传使用的也是这个子类。

注意:所有类型的task都要调用resume方法才会开始进行请求。

task本身有四种状态,task.status,用来判断当前任务的状态:

NSURLSessionTaskStateRunning    ///< 正在下载状态
   NSURLSessionTaskStateSuspended  ///< 暂停状态
   NSURLSessionTaskStateCanceling  ///< 取消任务状态
   NSURLSessionTaskStateCompleted  ///< 完成状态

task本身会有三个方法,结合这三个方法可以实现断点续传功能:

// 让当前的任务暂停,此时任务还可以被resume方法唤醒
- (void)suspend;
// 不仅可以启动任务,还可以唤醒suspend状态的任务
- (void)resume;
// 取消当前的任务,你也可以向处于suspend状态的任务发送cancel消息,任务如果被取消便不能再恢复到之前的状态
- (void)cancel;

4.NSURLSessionConfiguration

NSURLSessionConfiguration属于session的配置信息。如:

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 超时时间
config.timeoutIntervalForRequest = 10;
// 是否允许使用蜂窝网络(后台传输不适用)
config.allowsCellularAccess = YES;
// 还有很多可以设置的属性

Configuration有三种配置类型,我们通常用的是默认配置:

[NSURLSessionConfiguration defaultSessionConfiguration]
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier

这里表示了NSURLSession几种不同的工作模式.
默认的配置会将缓存存储在磁盘上
第二种瞬时会话模式不会创建持久性存储的缓存
第三种后台会话模式允许程序在后台进行上传下载工作.

除了支持任务的暂停和断点续传,我觉得NSURLSession之于NSURLConnection的最伟大的进步就是支持后台上传下载任务.

二、断点续传

1.首先我们使用的是抽象类NSURLSessionTask的一个字类NSURLSessionDataTask进行下载任务。

第一步:创建流和任务设置代理

<NSURLSessionDataDelegate>

@property (nonatomic , strong) NSOutputStream *stream;
@property (nonatomic , strong) NSURLSessionDataTask *task;
@property (nonatomic , assign) NSInteger totalLength;

第二步:创建下载任务请求

// 创建session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

// 创建流
// 这里的XXX是视频流下载后存储的路径
 self.stream = [NSOutputStream outputStreamToFileAtPath:XXX append:YES];

// 创建请求
// 这里的url是你的网络视频URL字符串
 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];

// 创建一个Data任务
self.task = [session dataTaskWithRequest:request];
// 利用KVC修改taskIdentifier的值,这是任务的标识
[task setValue:@(11111) forKeyPath:@"taskIdentifier"];     

[self.task resume];

3.NSURLSessionDataDelegate代理方法实现

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    // 打开流
    [self.stream open];

    // 获得服务器这次请求 返回数据的总长度
    self.totalLength = [response.allHeaderFields[@"Content-Length"] integerValue];

    // 接收这个请求,允许接收服务器的数据
    completionHandler(NSURLSessionResponseAllow);
}

// 接收到服务器返回的数据,一直回调,直到下载完成,暂停时会停止调用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    // 写入数据
    [self.stream write:data.bytes maxLength:data.length];

    // 下载进度
    NSUInteger receivedSize = [UIUtil getDownloadFileLengthPathWithUrl:@"1122aabb"];
    CGFloat progress = 1.0 * receivedSize / self.totalLength;

    NSLog(@"%.f%%",progress*100);
}

// 当任务下载完成或失败会调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error == nil) {
       // 下载成功 关闭流,取消任务
       [self.stream close];
       self.stream = nil;
       [self.task cancel];
       self.task = nil;
    }

}

这时候任务文件就开始下载了,需要暂停:

[self.task suspend];

继续下载:

[self.task resume];

到这里一个完整的断点续传就完成了,你会发现这里没有用到tmp临时文件,如果用子类NSURLSessionDownloadTask,需要你把tmp里面的下载文件转移到制定文件夹才可以。

提示:如果是多任务下载,可以利用model保存每个task,以及每个任务的总长度和已下载长度,一般用到了多任务下载都会牵扯到大量的数据保存,最好用FMDB数据库(或CoreData)保存。一般杀死后台进程后,再进入app,任务会处于暂停状态,需要你取出任务以及之前下载的进程,刷新任务下载列表,重新开启任务。