NSURLSession学习

一、回望NSURLConnection

作为Core Foundation / CFNetwork 框架的APIs之上的一个抽象,NSURLConnection伴随着2003年Safari浏览器的原始发行版本而诞生。

NSURLConnection实际上指的是一组构成Foundation框架中URL加载系统的相互关联的组件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache,NSHTTPCookieStorage,NSURLCredentialStorage,以及和它同名NSURLConnection。

二、从NSURLConnection到NSURLSession

在WWDC 2013中,Apple的团队对NSURLConnection进行了重构,并推出了NSURLSession在iOS9中,苹果已经废除了NSURLConnection的使用,使用NSURLSession代替。AFNetworking最新版也已经从依赖于NSURLConnection改为NSURLSession。

参见:https://developer.apple.com/library/prerelease/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html

NSURLSession也是一组相互依赖的类,它的大部分组件和NSURLConnection中的组件相同如NSURLRequest,NSURLCache等。而NSURLSession的不同之处在于,它将NSURLConnection替换为NSURLSessionNSURLSessionConfiguration,以及3个NSURLSessionTask的子类:NSURLSessionDataTask, NSURLSessionUploadTask, 和NSURLSessionDownloadTask。

在程序在前台时,NSURLSession与NSURLConnection可以互为替代工作;程序在后台或者程序未运行时,NSURLSession可以在后台继续工作。注意,如果用户强制将程序关闭,NSURLSession会断掉。

三、使用方法:

1.创建一个NSURLSessionConfiguration,为创建NSURLSession设置工作模式和网络;

sessionConfiguration一旦创建,再修改对已经创建的session就不起作用。除非在URLRequest中改写了更严格的策略。 

2.工作模式:

一般模式(default):工作模式类似于原来的NSURLConnection,可以使用缓存到硬盘的Cache,Cookie,鉴权。

及时模式(ephemeral):不使用缓存的Cache,Cookie,鉴权(session相关数据保存在内存中),适用于私密浏览和其它类似情况。

后台模式(background):在后台完成上传下载,创建Configuration对象的时候需要给一个NSString的ID用于追踪完成工作的Session是哪一个。

3.创建一个NSURLSession,方法有三种:

1 + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;

这种方法根据刚才创建的sessionConfiguration创建session,并默认创建一个新的queue处理session信息。

 1 + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration 2 delegate:(id<NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue; 

这种方法可以设置回调的delegate和queue,如果设置在mainQueue,就能在主线程进行回调。如果不指定线程,默认就是子线程。 
注意:session对delegateQueue是强应用,如果不停止session或者退出应用,它会一直存在在内存中。

1  + (NSURLSession *)sharedSession;

这种方法创建的session,使用使用共享的会话,全局的Cache,Cookie和证书。但是:不能逐渐获取服务器数据、自定义连接行为受限、鉴权受限、app未运行情况下无法后台上传和下载。

4.创建一个NSURLRequest调用刚才的NSURLSession对象提供的Task函数,创建一个NSURLSessionTask。 
根据职能不同Task有三种子类: 
1)NSURLSessionUploadTask:上传用的Task,传完以后不会再下载返回结果; 
通过request创建,在上传时指定文件源或数据源。

1 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
2 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
3 - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;

在创建upload task对象时,通过completionHandler指定任务完成后的回调代码块:

1 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
2 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;

2)NSURLSessionDownloadTask:下载用的Task; 
通过request对象或url创建:

1 - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
2 - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;

通过request对象或url创建,同时指定任务完成后通过completionHandler指定回调的代码块:

1 - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
2 - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;

根据已经下载的数据,继续下载任务,也有直接创建和任务完成后通过completionHandler指定回调的代码块两种:

1 - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
2 - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHa

3)NSURLSessionDataTask:可以上传内容,上传完成后再进行下载。 

通过request对象或url创建:

1 - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
2 - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request; 

通过request对象或url创建,同时指定任务完成后通过completionHandler指定回调的代码块:

1 - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  
2 - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  

 4.NSURLSessionDelegate

1)session失败时会调用,如果使用- (void)invalidateAndCancel;方法会立即调用,并给出错误原因

1 - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error;

2) 当遇到验证请求时会调用,completionHandler中应当包含如何处置验证请求和用于验证的证书

1 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

3)当后台session完成后会调用

1 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;

5.NSURLSessionDelegate

1)当task以错误结束的时候调用

1 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

2)task请求需要验证时调用

1 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

3)task向服务器发送数据时调用

1 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;

4)task需要向远程服务器发送一个新的body stream时调用

1 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler; 

5)task需要HTTP重定向时调用

1 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler

6.NSURLSessionDataDelegate

1)dataTask接收到响应时调用

1 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

2)dataTask变成downloadTask时调用

1 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask; 

1)dataTask接收到数据时调用 

1 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

 4)dataTask将缓存响应时调用

1 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler;

7.NSURLSessionDownloadDelegate

1)downloadTask继续下载时调用,可以用来暂停和恢复下载

1 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes;

2)downloadTask写入数据时调用,可以用来计算下载进度

1 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; 

3)downloadTask结束下载时调用

1 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;

8.使用详解

1)如果是downloadTask创建的Session调用,Session与Delegate会在指定的OperationQueue中进行交互,交互过程的顺序图如下(假如不需要鉴权,即非HTTPS请求): 
这里写图片描述 
2)当不再需要连接时: 
(1)调用Session的invalidateAndCancel直接关闭;

(2)或者调用finishTasksAndInvalidate等待当前Task全部结束后关闭;

这时Delegate会收到URLSession:didBecomeInvalidWithError:这个事件。Delegate收到这个事件之后会被解引用

3)如果是一个BackgroundSession:在Task执行的时候,用户切到后台,Session会和ApplicationDelegate做交互。当程序切到后台后,在BackgroundSession中的Task还会继续下载,现在分三个场景分析下Session和Application的关系:

(1)当加入了多个Task,程序没有切换到后台。 
这种情况Task会按照NSURLSessionConfiguration的设置正常下载,不会和ApplicationDelegate有交互。

(2)当加入了多个Task,程序切到后台,所有Task都完成下载。 
在切到后台之后,Session的Delegate不会再收到Task相关的消息,直到所有Task全都完成后,系统会调用ApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:回调,之后“汇报”下载工作,对于每一个后台下载的Task调用Session的Delegate中的URLSession:downloadTask:didFinishDownloadingToURL:(成功的话)和URLSession:task:didCompleteWithError:(成功或者失败都会调用) 
之后调用Session的Delegate回调URLSessionDidFinishEventsForBackgroundURLSession:。

注意:在ApplicationDelegate被唤醒后,会有个参数ComplietionHandler,这个参数是个Block,这个参数要在后面Session的Delegate中didFinish的时候调用一下,如下:


 1 - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier   completionHandler:(void (^)())completionHandler 
 2 {      
 3      /*Store the completion handler. The completion handler is invoked by the view controller's checkForAllDownloadsHavingCompleted method (if all the download tasks have been completed). */
 4       self.backgroundSessionCompletionHandler = completionHandler;  
 5  6 
 7 @end 
 8  
 9 //Session的Delegate 
10 @implementation APLViewController
11 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
12 {
13     APLAppDelegate *appDelegate = (APLAppDelegate *)
14     [[UIApplication sharedApplication] delegate];
15      if (appDelegate.backgroundSessionCompletionHandler) {
16         void (^completionHandler() = appDelegate.backgroundSessionCompletionHandler;
17             appDelegate.backgroundSessionCompletionHandler = nil;
18               completionHandler();
19     }
20     NSLog(@"All tasks are finished");
21 }
22 @end

(3)当加入了多个Task,程序切到后台,下载完成了几个Task,然后用户又切换到前台。(程序没有退出)

切到后台之后,Session的Delegate仍然收不到消息。在下载完成几个Task之后再切换到前台,系统会先汇报已经下载完成的Task的情况,然后继续下载没有下载完成的Task,后面的过程同第一种情况。 

(4)当加入了多个Task,程序切到后台,几个Task已经完成,但还有Task还没有下载完的时候强制退出程序,然后再进入程序的时候。(程序退出了)

最后这个情况比较有意思,由于程序已经退出了,后面没有下完Session就不在了后面的Task肯定是失败了。但是已经下载成功的那些Task,新启动的程序也没有听“汇报”的机会了。

经过实验发现,这个时候之前在NSURLSessionConfiguration设置的NSString类型的ID起作用了,当ID相同的时候,一旦生成Session对象并设置Delegate,马上可以收到上一次关闭程序之前没有汇报工作的Task的结束情况(成功或者失败)。但是当ID不相同,这些情况就收不到了,因此为了不让自己的消息被别的应用程序收到,或者收到别的应用程序的消息,ID还是和程序的Bundle名称绑定上比较好,至少保证唯一性。

原文地址:https://www.cnblogs.com/xiayao/p/5260227.html