NSURLSession是苹果在WWDC2013中提出来的,旨在替代NSURLConnection,与我们之前经常使用的NSURLConnection不同,NSURLSession为我们提供了更灵活的使用方法,包括后台下载以及断点续传的实现等功能.之前使用下载一直用的都是第三方框架比如OC的AFNetworking或者Swift的Alamofire.虽然第三方库用起来很方便也很稳定,但是还是想自己研究下苹果原生的下载框架.这几天研究了下NSURLSession,做了一个加载视频的demo,包括了视频下载,断点续传的功能,自己简单总结下,也算是对自己学习过程的一个记录,嘿嘿.
和使用NSURLConnection下载的时候创建NSURLConnection对象之前发起同步或者异步请求不同,NSURLSession我们使用的是它的三个子类,先简单说下NSURLSession的使用流程吧:
- 创建 NSURLSessionConfiguration
- 使用 NSURLSessionConfiguration创建NSURLSession
- 使用NSURLSession来创建NSURLSessionTask对象
- 使用NSURLSessionTask对象来下载文件
具体介绍下这三个东西是用来干嘛的:
- NSURLSessionConfiguration
NSURLSessionConfiguration是NSURLSession的配置文件,其中包括了缓存策略,请求超时时长以及使用什么网络类型请求等属性,其创建方法有三种:
+(NSURLSessionConfiguration *)defaultSessionConfiguration; //默认模式,可以使用缓存的Cache,Cookie
+(NSURLSessionConfiguration *)ephemeralSessionConfiguration;//瞬时会话模式 不可以使用缓存的Cache,Cookie,鉴权
+(NSURLSessionConfiguration*)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);//后台模式 可以在后台进行下载
NSURLSessionConfiguration有个重要的属性
/* allow request to route over cellular. */
@property BOOL allowsCellularAccess;
/* allows background tasks to be scheduled at the discretion of the system for optimal performance. */
@property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(10_10, 7_0);
allowsCellularAccess属性为是否允许在移动网络下下载文件,一般情况下建议使用discretionary,该属性允许应用自动判断当前使用哪种网络下载比较好.
- NSURLSession
NSURLSession是用来生成和管理任务的,它有三种创建模式,如下所示
+ (NSURLSession *)sharedSession;
创建全局的session,返回共享的会话,使用全局的Cache、Cookie和证书
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
使用自己创建的配置文件创建session
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
使用自己创建的配置文件创建session,并且可以设置他的代理,用来监听他的代理事件,从而监听下载进度以及将下载好的文件转移等操作,用起来十分方便,也是我们最经常用的一种模式
重点是第三种创建方法,这也是最经常使用的方法
+(NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
重点说一下其参数意义:
1. configuration:我们创建的配置文件,传进去就可以了
2. delegate: 一般设置自己为其代理
3. delegateQueue: 方法回调队列,这个需要我们传一个NSOperationQueue队列进去,其代理回调函数都会在我们传进去的队列执行.如果我们传nil的话,其代理回调函数则会在添加到全局并发队列里去,将会开启多个线程来执行任务下载.
- NSURLSessionTask
任务对象,也是我们开启下载的对象,它有三个子类,我们使用的时候也是直接使用它的子类
NSURLSessionDataTask
NSURLSessionUploadTask
NSURLSessionDownloadTask
作用不言而喻,上传时候使用NSURLSessionUploadTask,下载的时候使用NSURLSessionDownloadTask,至于NSURLSessionDataTask,我们看下苹果的官方说明:
/*
* An NSURLSessionDataTask does not provide any additional
* functionality over an NSURLSessionTask and its presence is merely
* to provide lexical differentiation from download and upload tasks.
*/
@interface NSURLSessionDataTask : NSURLSessionTask
翻译:相比于NSURLSessionTask她不提额外的功能,它的存在仅仅是为了和download以及upload词汇方面的不同. 呵呵哒
基本上,我们上传和下载使用那两个类就足够了,至于这个为了提供词汇不同的NSURLSessionDataTask,基本上用不到.
以NSURLSessionDownloadTask为例,我们看下他的创建方法:
/* Creates a download task with the given request. */
使用我们创建好的request来创建下载任务,和NSURLConnection使用方法类似
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
/* Creates a download task to download the contents of the given URL. */
直接使用链接创建下载任务
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
/* Creates a download task with the resume data. If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */
使用续传数据来创建下载任务
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
创建好之后,开启任务的方法:
[self.downloadTask resume]; //开始下载文件
- 创建示例
说了这么多,来看下具体的使用示例吧,提供一个简单完整的创建流程和开启任务的方法,保证看完秒懂 0.0:
示例代码:
//创建configuration
NSURLSessionConfiguration *configuration ==[NSURLSessionConfiguration defaultSessionConfiguration];
configuration.discretionary = YES;
//使用创建的configuration创建session 并将自己设置为代理
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
//创建request k_DownloadURLStr是我定义的宏定义网址链接
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:k_DownloadURLStr]];
//使用创建的session和request创建NSURLSessionDownloadTask
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
//开启下载任务
[downloadTask resume];
[downloadTask invalidateAndCancel]; 关闭下载任务
[downloadTask finishTasksAndInvalidate]; 等待当前Task结束后关闭
既然设置自己为session的代理,需要实现的代理协议为NSURLSessionDownloadDelegate,一般使用NSURLSessionDownloadDelegate代理中的以下三个方法:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
当下载任务开启之后,一旦有数据返回,那么这个方法就会被调用,其参数意义如下:
bytesWritten : 本次调用返回了多少字节的数据
totalBytesWritten: 一共返回了多少字节的数据
totalBytesExpectedToWrite: 该下载文件的大小
通过这个方法,我们就可以实时的监听下载的进度了
当下载完成之后,会调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
值得一提的是,当文件正在下载的时候,文件是下载到沙盒中的tmp文件夹中的,当下载完成之后,文件仍然存在于该文件夹中.但是该文件夹中的内容当iphone重新启动的时候就会被清空,所以我们需要将下载好的文件转移到cache文件夹中保存起来,给出一个操作示例
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSURL *urlOfSave = [NSURL fileURLWithPath:paths[0]];
urlOfSave = [urlOfSave URLByAppendingPathComponent:@"冰河世纪.mov"];
NSLog(@"%@",urlOfSave);
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:urlOfSave.path]) {
//如果文件夹下有同名文件 则将其删除
[fileManager removeItemAtURL:urlOfSave error:nil];
}
//将下载好的文件复制到存储的文件夹下
[fileManager copyItemAtURL:location toURL:urlOfSave error:nil];
[self.session invalidateAndCancel];
self.session = nil;
}
当我们关闭任务或者手动结束任务的时候,会调用:
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;
- 断点续传的实现
如果要实现断点续传功能的话,首先要调用方法
__weak typeof (self)weakSelf = self;
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
weakSelf.downloadData = resumeData;
}];
downloadData是我创建的全局变量,用来保存中断的数据,
接着需要使用方法
self.downloadTask = [self.session downloadTaskWithResumeData:self.downloadData];
[self.downloadTask resume];
来重新开启下载任务
其中resumeData中保存的并不是当前下载的文件,将其拿到转出来的内容是xml报文,内容如下所示,(部分冗余的内容我已经切掉了),部分地方我已经添加注释.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSURLSessionDownloadURL</key>
<string>http://oarbi0614.bkt.clouddn.com/%E5%86%B0%E6%B2%B3%E4%B8%96%E7%BA%AA.mp4</string> 请求的地址
<key>NSURLSessionResumeBytesReceived</key>
<integer>12038723</integer> 当前下载的文件大小
<key>NSURLSessionResumeCurrentRequest</key>
<data>
YnBsaXN0MDD.....
</data>
<key>NSURLSessionResumeEntityTag</key>
<string>"ljECR82nRMhHvP8D5M9sGQuKBjgK"</string>
<key>NSURLSessionResumeInfoTempFileName</key>
<string>CFNetworkDownload_6XdKfZ.tmp</string> 下载使用的临时文件名
<key>NSURLSessionResumeInfoVersion</key>
<integer>2</integer>
<key>NSURLSessionResumeOriginalRequest</key>
<data>
YnBsaXN0MDD...
</data>
<key>NSURLSessionResumeServerDownloadDate</key>
<string>Mon, 25 Jul 2016 10:42:23 GMT</string>
</dict>
</plist>
当我们暂停之后拿到resumeData,再重新使用resumeData下载的时候,session会使用该数据块中的信息去查找临时文件,然后继续下载.这样的话,我们也就实现了断点续传的功能.
- 延伸
这个虽然实现了断点续传的功能,但是如果应用在运行的时候被杀死,那么下次下载的时候仍然是需要重新下载,上次下载的数据仍然会丢失,所以说,该断点续传仅仅是使用在程序正常运行过程中的暂停然后可以接着下载,如果程序意外终结那么下载数据仍然会丢失.关于这个问题,我想了很多的解决方案,在下一篇博客,我会具体说明我是如何实现的,也欢迎大家随时指教. - 结语
上面的动图是我做的一个demo,包括了视频的下载,播放,暂停,恢复播放的功能.其使用的加载百分比视图是自己封装的一个小控件,视频播放使用的是AVPlayer.该demo放在了gitHub上面,地址:https://github.com/TheRuningAnt/TestSession.git
欢迎各位下载查看.喜欢的话记得给我一个星星哦.另外,该博文中有任何错误或不足之处,还请各位看官不吝赐教.多谢!