原文链接:http://blog.csdn.net/mumubumaopao/article/details/52074367
NSURLSession是苹果在WWDC2013中提出来的,旨在替代NSURLConnection,与我们之前经常使用的NSURLConnection不同,NSURLSession为我们提供了更灵活的使用方法,包括后台下载以及断点续传的实现等功能.之前使用下载一直用的都是第三方框架比如OC的AFNetworking或者
Swift
的Alamofire.虽然第三方库用起来很方便也很稳定,但是还是想自己研究下苹果原生的下载框架.这几天研究了下NSURLSession,做了一个加载视频的demo,包括了视频下载,断点续传的功能,自己简单总结下,也算是对自己学习过程的一个记录,嘿嘿.
注:iOS9中引入了新特性App Transport Security (ATS),该特性要求App内访问网站必须使用HTTPS协议。我们可以暂时关闭该协议:
1. 在Info.plist中添加新项NSAppTransportSecurity,类型为Dictionary。
2. 在NSAppTransportSecurity下添加子项NSAllowsArbitraryLoads,类型为Boolean,值设为YES。
和使用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 requesttorouteovercellular. */@propertyBOOL allowsCellularAccess; /* allows background taskstobe scheduledatthediscretionofthesystemforoptimal 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)delegate delegateQueue:(nullable NSOperationQueue *)queue;使用自己创建的配置文件创建session,并且可以设置他的代理,用来监听他的代理事件,从而监听下载进度以及将下载好的文件转移等操作,用起来十分方便,也是我们最经常用的一种模式
重点是第三种创建方法,这也是最经常使用的方法
+(NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configurationdelegate:(nullableid )delegatedelegateQueue:(nullableNSOperationQueue *)queue;
重点说一下其参数意义:
1. configuration:我们创建的配置文件,传进去就可以了
2. delegate: 一般设置自己为其代理
3. delegateQueue: 方法回调队列,这个需要我们传一个NSOperationQueue队列进去,其代理回调函数都会在我们传进去的队列执行.如果我们传nil的话,其代理回调函数则会在添加到全局并发队列里去,将会开启多个线程来执行任务下载.
NSURLSessionTask
任务对象,也是我们开启下载的对象,它有三个子类,我们使用的时候也是直接使用它的子类
NSURLSessionDataTask
NSURLSessionUploadTask
NSURLSessionDownloadTask
作用不言而喻,上传时候使用NSURLSessionUploadTask,下载的时候使用NSURLSessionDownloadTask,至于NSURLSessionDataTask,我们看下苹果的官方说明:
/* * An NSURLSessionDataTaskdoesnotprovide any additional * functionalityoveran NSURLSessionTaskanditspresenceismerely *toprovide lexical differentiationfromdownloadandupload tasks. */@interface NSURLSessionDataTask : NSURLSessionTask
翻译:相比于NSURLSessionTask她不提额外的功能,它的存在仅仅是为了和download以及upload词汇方面的不同. 呵呵哒
基本上,我们上传和下载使用那两个类就足够了,至于这个为了提供词汇不同的NSURLSessionDataTask,基本上用不到.
以NSURLSessionDownloadTask为例,我们看下他的创建方法:
/* Creates a download taskwiththegivenrequest. */使用我们创建好的request来创建下载任务,和NSURLConnection使用方法类似- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;/* Creates a download tasktodownloadthecontentsofthegivenURL. */直接使用链接创建下载任务- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;/* Creates a download taskwiththeresume data. Ifthedownload cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */使用续传数据来创建下载任务- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
创建好之后,开启任务的方法:
[self.downloadTask resume]; //开始下载文件
创建示例
说了这么多,来看下具体的使用示例吧,提供一个简单完整的创建流程和开启任务的方法,保证看完秒懂 0.0:
示例代码://创建configurationNSURLSessionConfiguration *configuration ==[NSURLSessionConfiguration defaultSessionConfiguration]; configuration.discretionary=YES;//使用创建的configuration创建session 并将自己设置为代理NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:selfdelegateQueue:nil];//创建request k_DownloadURLStr是我定义的宏定义网址链接NSURLRequest*request = [NSURLRequestrequestWithURL:[NSURLURLWithString:k_DownloadURLStr]];//使用创建的session和request创建NSURLSessionDownloadTaskNSURLSessionDownloadTask *downloadTask = [self.sessiondownloadTaskWithRequest: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)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite当下载任务开启之后,一旦有数据返回,那么这个方法就会被调用,其参数意义如下:bytesWritten : 本次调用返回了多少字节的数据totalBytesWritten: 一共返回了多少字节的数据totalBytesExpectedToWrite: 该下载文件的大小通过这个方法,我们就可以实时的监听下载的进度了
当下载完成之后,会调用
-(void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTaskdidFinishDownloadingToURL:(NSURL*)location
值得一提的是,当文件正在下载的时候,文件是下载到沙盒中的tmp文件夹中的,当下载完成之后,文件仍然存在于该文件夹中.但是该文件夹中的内容当iphone重新启动的时候就会被清空,所以我们需要将下载好的文件转移到cache文件夹中保存起来,给出一个操作示例
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL*)location{NSArray*paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask,YES);NSURL*urlOfSave = [NSURLfileURLWithPath:paths[0]]; urlOfSave = [urlOfSave URLByAppendingPathComponent:@"冰河世纪.mov"];NSLog(@"%@",urlOfSave);NSFileManager*fileManager = [NSFileManagerdefaultManager];if([fileManager fileExistsAtPath:urlOfSave.path]) {//如果文件夹下有同名文件 则将其删除[fileManager removeItemAtURL:urlOfSave error:nil]; }//将下载好的文件复制到存储的文件夹下[fileManager copyItemAtURL:location toURL:urlOfSave error:nil]; [self.sessioninvalidateAndCancel];self.session=nil;}
当我们关闭任务或者手动结束任务的时候,会调用:
-(void)URLSession:(NSURLSession*)session didBecomeInvalidWithError:(nullableNSError*)error;
断点续传的实现
如果要实现断点续传功能的话,首先要调用方法 __weaktypeof (self)weakSelf =self; [self.downloadTaskcancelByProducingResumeData:^(NSData * _Nullable resumeData) { weakSelf.downloadData= resumeData; }]; downloadData是我创建的全局变量,用来保存中断的数据, 接着需要使用方法self.downloadTask= [self.sessiondownloadTaskWithResumeData:self.downloadData]; [self.downloadTaskresume]; 来重新开启下载任务
其中resumeData中保存的并不是当前下载的文件,将其拿到转出来的内容是xml报文,内容如下所示,(部分冗余的内容我已经切掉了),部分地方我已经添加注释.
NSURLSessionDownloadURLhttp://oarbi0614.bkt.clouddn.com/%E5%86%B0%E6%B2%B3%E4%B8%96%E7%BA%AA.mp4请求的地址NSURLSessionResumeBytesReceived12038723当前下载的文件大小NSURLSessionResumeCurrentRequestYnBsaXN0MDD.....NSURLSessionResumeEntityTag"ljECR82nRMhHvP8D5M9sGQuKBjgK"NSURLSessionResumeInfoTempFileNameCFNetworkDownload_6XdKfZ.tmp下载使用的临时文件名NSURLSessionResumeInfoVersion2NSURLSessionResumeOriginalRequestYnBsaXN0MDD...NSURLSessionResumeServerDownloadDateMon, 25 Jul 2016 10:42:23 GMT
当我们暂停之后拿到resumeData,再重新使用resumeData下载的时候,session会使用该数据块中的信息去查找临时文件,然后继续下载.这样的话,我们也就实现了断点续传的功能.
延伸
这个虽然实现了断点续传的功能,但是如果应用在运行的时候被杀死,那么下次下载的时候仍然是需要重新下载,上次下载的数据仍然会丢失,所以说,该断点续传仅仅是使用在程序正常运行过程中的暂停然后可以接着下载,如果程序意外终结那么下载数据仍然会丢失.关于这个问题,我想了很多的解决方案,在下一篇博客,我会具体说明我是如何实现的,也欢迎大家随时指教.
结语
上面的动图是我做的一个demo,包括了视频的下载,播放,暂停,恢复播放的功能.其使用的加载百分比视图是自己封装的一个小控件,视频播放使用的是AVPlayer.该demo放在了gitHub上面(该demo基于iphone6s plus尺寸开发,仅为展示效果,未做屏幕适配,请稍加留意),地址:https://github.com/TheRuningAnt/TestSession.git
欢迎各位下载查看.喜欢的话记得给我一个星星哦.另外,该博文中有任何错误或不足之处,还请各位看官不吝赐教.多谢!
加入审核被拒交流群,一起交流审核上架经验吧~~ 群号:689757099