NSURLSession

NSURLSession概述1. NSURLSession session类型NSURLSession包括下面3种session类型a). Default session(默认会话模式):使用的是基于磁盘缓存的持久化策略,工作模式类似于原来的NSURLConnection,可以用来取代NSURLConnection中的:[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]b). Ephemeral session(瞬时会话模式):临时的进程内会话(内存),不会将cookie、证书、缓存储存到本地,只会放到内存中,当应用程序退出后数据也会消失。c). Background session(后台会话模式):和默认会话模式类似, 不过相比默认模式,该会话会在后台开启一个线程进行网络数据处理。//Default session+ (NSURLSessionConfiguration *)defaultSessionConfiguration;//Ephemeral+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;//Background + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;你需要了解包括NSURLSession、NSURLSessionConfiguration 以及 NSURLSessionTask 的 3 个子类:NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。与 NSURLConnection 相比,NSURLsession 最直接的改进就是可以配置每个 session 的缓存,协议,cookie,以及证书策略,甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。每个 NSURLSession 对象都由一个 NSURLSessionConfiguration 对象来进行初始化。NSURLSession 中另一大块就是 session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。NSURLSessionTask 与 NSURLConnection 最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者 NSURLSession 这一公共委托者(common delegate)。2.NSURLSessionTask类NSURLSessionTask是一个抽象子类,它有三个子类:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。这三个类封装了现代应用程序的三个基本网络任务:获取数据,比如JSON或XML,以及上传和下载文件。下面是其继承关系:不同于直接使用 alloc-init 初始化方法,task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有 completionHandler 这个 block 的两个版本。1).NSURLSessionDataTaskNSURLSessionDataTask使用NSData来交换数据. NSURLSessionDataTask不支持后台会话模式。a) 通过request对象或url创建//通过一个给定的请求- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;//通过一个给定的URL.- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;代理方法:- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTaskdidReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{    completionHandler(NSURLSessionResponseAllow);}- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask    didReceiveData:(NSData *)data{    //data: response from the server.}b).通过request对象或url创建,同时指定任务完成后通过completionHandler指定回调的代码块:- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;2).NSURLSessionDownloadTaska).通过URL/request/resumeData创建- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;//下载任务支持断点续传,这种方式是通过之前已经下载的数据来创建下载任务。- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;代理方法:- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask                              didFinishDownloadingToURL:(NSURL *)location;- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask                                          didWriteData:(int64_t)bytesWritten                                      totalBytesWritten:(int64_t)totalBytesWritten                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask                                      didResumeAtOffset:(int64_t)fileOffset                                    expectedTotalBytes:(int64_t)expectedTotalBytes;b).通过completionHandler指定任务完成后的回调代码块:- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;3).NSURLSessionUploadTaska).通过request创建,在上传时指定文件源或数据源。- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;代理方法:- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task                                didSendBodyData:(int64_t)bytesSent                                totalBytesSent:(int64_t)totalBytesSent                      totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;b).通过completionHandler指定任务完成后的回调代码块- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;3.NSURLSession类从上面我们已经知道创建一个NSURLSessionTask需要NSURLSession,获取NSURLSession类对象有几种方式://使用全局的Cache,Cookie和证书。+ (NSURLSession *)sharedSession;  //创建对应配置的会话。  + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id)delegate delegateQueue:(NSOperationQueue *)queue;第三种方式指定了session的委托和委托所处的队列。当不再需要连接时,可以调用Session的invalidateAndCancel直接关闭,或者调用finishTasksAndInvalidate等待当前Task结束后关闭。这时Delegate会收到URLSession:didBecomeInvalidWithError:这个事件。Delegate收到这个事件之后会被解引用。3.NSURLSessionConfiguration类上面2、3种创建方式使用了NSURLSessionConfiguration,它用于配置会话的属性,创建方法如下:+ (NSURLSessionConfiguration *)defaultSessionConfiguration;  + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;  //identifier参数指定了会话的ID,用于标记后台的session。+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;这个类有两个属性:@property BOOL allowsCellularAccess;  @property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(NA, 7_0);allowsCellularAccess 属性指定是否允许使用蜂窝连接, discretionary属性为YES时表示当程序在后台运作时由系统自己选择最佳的网络连接配置,该属性可以节省通过蜂窝连接的带宽。在使用后台传输数据的时候,建议使用discretionary属性,而不是allowsCellularAccess属性,因为它会把WiFi和电源可用性考虑在内。2.实例创建一个NSURLSession任务需要遵循以下步骤:a)创建一个Session Configurationb)创建一个NSURLSessionc) 创建一个NSURLSession Task ( Data,Download or Upload)d)代理方法e)调用resume方法1.数据请求前面通过请求一个NSURLSessionDataTask进行数据请求演示:- (IBAction)loadData:(UIButton *)sender {    // 创建Data Task,    NSURL *url = [NSURL URLWithString:@"http://www.jianshu.com/users/9tsPFp/latest_articles"];    NSURLRequest *request = [NSURLRequest requestWithURL:url];    NSURLSession *session = [NSURLSession sharedSession];    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request                                                completionHandler:                                      ^(NSData *data, NSURLResponse *response, NSError *error) {                                          // 输出返回的状态码,请求成功的话为200                                          if (!error) {                                              [self showResponseCode:response];                                          }else{                                              NSLog(@"error is :%@",error.localizedDescription);                                          }                                      }];    // 使用resume方法启动任务    [dataTask resume];}- (void)showResponseCode:(NSURLResponse *)response {    NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;    NSInteger responseStatusCode = [httpResponse statusCode];    NSLog(@"状态码--%ld", (long)responseStatusCode);}2.HTTP GET和POST1).GET我们使用豆瓣的API来演示这个GET请求- (IBAction)GETRequest:(id)sender {    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];    NSURL * url = [NSURL URLWithString:@"https://api.douban.com/v2/book/1220562"];    NSURLSessionDataTask * dataTask = [delegateFreeSession dataTaskWithURL:url                                                        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {                                                            if(error == nil)                                                            {                                                                NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];                                                                NSLog(@"GET请求数据= %@",text);                                                            }                                                        }];    [dataTask resume];}数据请求结果如下:2).POST我们使用http://hayageek.com/examples/jquery/ajax-post/ajax-post.php 这个接口来演示POST请求,POST需要4个参数: name, loc, age, submit,如下:- (IBAction)POSTRequest:(UIButton *)sender {    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];    NSURL * url = [NSURL URLWithString:@"http://hayageek.com/examples/jquery/ajax-post/ajax-post.php"];    NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];    NSString * params =@"name=Ravi&loc=India&age=31&submit=true";    [urlRequest setHTTPMethod:@"POST"];    [urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];    NSURLSessionDataTask * dataTask =[delegateFreeSession dataTaskWithRequest:urlRequest                                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {                                                                NSLog(@"Response:%@ %@\n", response, error);                                                                if(error == nil)                                                                {                                                                    NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];                                                                    NSLog(@"POST返回数据 = %@",text);                                                                }                                                            }];    [dataTask resume];}返回数据:Response:{ URL: http://hayageek.com/examples/jquery/ajax-post/ajax-post.php } { status code: 200, headers {    Connection = "keep-alive";    "Content-Encoding" = gzip;    "Content-Type" = "text/html";    Date = "Wed, 18 Mar 2015 01:43:07 GMT";    Server = nginx;    "Transfer-Encoding" = Identity;    Vary = "Accept-Encoding, Accept-Encoding";    "X-Powered-By" = "EasyEngine 2.0.0";} } (null)2015-03-18 09:43:10.418 WebConnectionDemo[28129:2666176] POST返回数据 = Data from server: {"name":"Ravi","loc":"India","age":"31","submit":"true"}

3.NSURLSessionUploadTaskUpload task 的创建需要使用一个 request,另外加上一个要上传的 NSData 对象或者是一个本地文件的路径对应的 NSURL,类似的代码如下:NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSData *data = ...; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request                                                            fromData:data                                                  completionHandler:    ^(NSData *data, NSURLResponse *response, NSError *error) {        // ...    }]; [uploadTask resume];4.NSURLSessionDownloadTaska.普通下载- (IBAction)download:(id)sender {    NSURL * url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/pic/item/63d0f703918fa0ec14b94082249759ee3c6ddbc6.jpg"];    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];    NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url];    [downloadTask resume];}为了实现下载进度的显示,需要在委托中的以下方法中实现:-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{    float progress = totalBytesWritten*1.0/totalBytesExpectedToWrite;    dispatch_async(dispatch_get_main_queue(),^ {        [self.process setProgress:progress animated:YES];    });    NSLog(@"Progress =%f",progress);    NSLog(@"Received: %lld bytes (Downloaded: %lld bytes)  Expected: %lld bytes.\n",          bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);}Download task 是将数据一点点地写入本地的临时文件。我们需要把文件从一个临时地址移动到一个永久的地址保存起来::-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{    NSLog(@"Temporary File :%@\n", location);    NSError *err = nil;    NSFileManager *fileManager = [NSFileManager defaultManager];    NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];    NSURL *docsDirURL = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent:@"out1.zip"]];    if ([fileManager moveItemAtURL:location                            toURL:docsDirURL                            error: &err])    {        NSLog(@"File is saved to =%@",docsDir);    }    else    {        NSLog(@"failed to move: %@",[err userInfo]);    }}如下所示:b.可取消的下载我们将做一些改动,使其支持取消下载,先创建一个全局NSURLSessionDownloadTask对象:@property (strong, nonatomic) NSURLSessionDownloadTask *cancellableTask;调用cancle方法即可取消:- (IBAction)cancleDownload:(UIButton *)sender {    if (self.cancellableTask) {        [self.cancellableTask cancel];        self.cancellableTask = nil;    }}这样点击cancle按钮后下载任务会取消,重新点击下载会从最初的经度条开始下载。c.断点续传下载断点续传,我们需要一个NSData来暂存我们下载的数据:@property (strong, nonatomic) NSData *partialData;download方法中做以下改动,如果已经有缓存的数据,即使用downloadTaskWithResumeData进行下载:- (IBAction)download:(UIButton *)sender {    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];    if (self.partialData) {        self.cancellableTask = [defaultSession downloadTaskWithResumeData:self.partialData];    }    else{    NSURL * url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/pic/item/63d0f703918fa0ec14b94082249759ee3c6ddbc6.jpg"];    self.cancellableTask =[ defaultSession downloadTaskWithURL:url];    }    [self.cancellableTask resume];}cancle方法也需要做改动,我们需要保存我们已下载的数据:- (IBAction)cancleDownload:(UIButton *)sender {    if (self.cancellableTask) {        [self.cancellableTask cancelByProducingResumeData:^(NSData *resumeData) {            self.partialData = resumeData;        }];        self.cancellableTask = nil;    }}另外恢复下载时,NSURLSessionDownloadDelegate中的以下方法将被调用:-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{    NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);}如下:6.后台下载首先创建一个全局对象:@property (strong, nonatomic) NSURLSessionDownloadTask * backgroundDownloadTask;配置下载任务:- (IBAction)downoadBackground:(id)sender {    NSURL * url = [NSURL URLWithString:@"https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/CocoaEncyclopedia.pdf"];    NSURLSessionConfiguration * backgroundConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundtask1"];    NSURLSession *backgroundSeesion = [NSURLSession sessionWithConfiguration: backgroundConfig delegate:self delegateQueue: [NSOperationQueue mainQueue]];    self.backgroundDownloadTask =[ backgroundSeesion downloadTaskWithURL:url];    [self.backgroundDownloadTask resume];}在程序进入后台后,如果下载任务完成,AppDelegate中的对应方法将被调用:- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{    NSLog(@"Save completionHandler");    self.completionHandler = completionHandler;}然后修改上面那个协议方法,如下:-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{    if (downloadTask == self.cancellableTask) {        NSLog(@"Temporary File :%@\n", location);        NSError *err = nil;        NSFileManager *fileManager = [NSFileManager defaultManager];        NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];        NSURL *docsDirURL = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent:@"out1.zip"]];        if ([fileManager moveItemAtURL:location                                toURL:docsDirURL                                error: &err]){            NSLog(@"File is saved to =%@",docsDir);        }        else{            NSLog(@"failed to move: %@",[err userInfo]);        }    }else if(downloadTask == self.backgroundDownloadTask){        NSLog(@"Background URL session %@ finished events.\n", session);        AppDelegate * delegate =(AppDelegate *)[[UIApplication sharedApplication] delegate];        if(delegate.completionHandler)        {            void (^handler)() = delegate.completionHandler;            handler();        }    }}运行程序并退出当前程序后打印结果如下:Received: 21127 bytes (Downloaded: 1439932 bytes)  Expected: 1464162 bytes.Progress =0.997860Received: 21096 bytes (Downloaded: 1461028 bytes)  Expected: 1464162 bytes.Progress =1.000000Received: 3134 bytes (Downloaded: 1464162 bytes)  Expected: 1464162 bytes.Background URL session <__NSURLBackgroundSession: 0x7fc5c357b560> finished events.关于NSURLSessionConfiguration的配置策略你可以在这篇文章中找到,下一节我们讲AFNetworking和WebView。

推荐阅读更多精彩内容