NSURLSession学习笔记(一)简介html
1、URL Session的基本概念ios
1.三种工做模式:git
默认会话模式(default):工做模式相似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证受权。github
瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。全部和会话相关的caches,证书,cookies等都被保存在RAM中,所以当程序使会话无效,这些缓存的数据就会被自动清空。web
后台会话模式(background):该模式在后台完成上传和下载,在建立Configuration对象的时候须要提供一个NSString类型的ID用于标识完成工做的后台会话。xcode
2.NSURLSession支持的三种任务缓存
NSURLSession类支持三种类型的任务:加载数据,下载和上传。性能优化
2、相关的类cookie
NSURLConnection这个名字,实际上指的是一组构成Foundation框架中URL加载系统的相互关联的组件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache,NSHTTPCookieStorage,NSURLCredentialStorage,以及和它同名的NSURLConnection。网络
在WWDC 2013中,Apple的团队对NSURLConnection进行了重构,并推出了NSURLSession做为替代。
NSURLSession也是一组相互依赖的类,它的大部分组件和NSURLConnection中的组件相同如NSURLRequest,NSURLCache等。而NSURLSession的不一样之处在于,它将NSURLConnection替换为NSURLSession和NSURLSessionConfiguration,以及3个NSURLSessionTask的子类:NSURLSessionDataTask, NSURLSessionUploadTask, 和NSURLSessionDownloadTask。
下面来讲下NSURLSession新推出的类:
1.NSURLSessionConfiguration类
其中NSURLSessionConfiguration用于配置会话的属性,能够经过该类配置会话的工做模式:
- + (NSURLSessionConfiguration *)defaultSessionConfiguration;
- + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
- + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;
在backgroundSessionConfiguration:方法中的identifier参数指定了会话的ID,用于标记后台的session。
该类的其中两个属性:
- @property BOOL allowsCellularAccess;
-
- @property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(NA, 7_0);
allowsCellularAccess 属性指定是否容许使用蜂窝链接, discretionary属性为YES时表示当程序在后台运做时由系统本身选择最佳的网络链接配置,该属性能够节省经过蜂窝链接的带宽。在使用后台传输数据的时候,建议使用discretionary属性,而不是allowsCellularAccess属性,由于它会把WiFi和电源可用性考虑在内。补充:这个标志容许系统为分配任务进行性能优化。这意味着只有当设备有足够电量时,设备才经过Wifi进行数据传输。若是电量低,或者只仅有一个蜂窝链接,传输任务是不会运行的。后台传输老是在discretionary模式下运行。
2.NSURLSession类
获取NSURLSession类对象有几种方式:
- + (NSURLSession *)sharedSession;
-
- + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
- + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;
第一种方式是使用静态的sharedSession方法,该类使用共享的会话,该会话使用全局的Cache,Cookie和证书。
第二种方式是经过sessionWithConfiguration:方法建立对象,也就是建立对应配置的会话,与NSURLSessionConfiguration合做使用。
第三种方式是经过sessionWithConfiguration:delegate:delegateQueue方法建立对象,二三两种方式能够建立一个新会话并定制其会话类型。该方式中指定了session的委托和委托所处的队列。当再也不须要链接时,能够调用Session的invalidateAndCancel直接关闭,或者调用finishTasksAndInvalidate等待当前Task结束后关闭。这时Delegate会收到URLSession:didBecomeInvalidWithError:这个事件。Delegate收到这个事件以后会被解引用。
3.NSURLSessionTask类
NSURLSessionTask是一个抽象子类,它有三个子类:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。这三个类封装了现代应用程序的三个基本网络任务:获取数据,好比JSON或XML,以及上传和下载文件。
下面是其继承关系:

有多种方法建立对应的任务对象:
(1)NSURLSessionDataTask
经过request对象或url建立:
- - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
-
- - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
经过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)NSURLSessionUploadTask
经过request建立,在上传时指定文件源或数据源。
- - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
-
- - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
-
- - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
在建立upload task对象时,经过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)NSURLSessionDownloadTask
- - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
-
- - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
-
- - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
下载任务支持断点续传,第三种方式是经过以前已经下载的数据来建立下载任务。
一样地能够经过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;
4.NSURLSessionDelegate和NSURLSessionTaskDelegate协议
在协议的方法中能够完成各类各样的回调动做,如身份验证、完成任务后的动做、错误处理和后台任务完成的动做等。委托方法指定在NSURLSession中必定数量的字节传输使用int64_t类型的参数。
这里只说下后台任务的一个委托方法:
- - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session NS_AVAILABLE_IOS(7_0);
合做使用的ApplicationDelegate方法:
- - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler NS_AVAILABLE_IOS(7_0);
将任务切换到后台以后,Session的Delegate不会再收到和Task相关的消息。当全部Task全都完成后,程序将被唤醒,并调用ApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:回调,在这里要为后台session(由background session的identifier标识)指定对应的回调代码块。
随后,对于每个完成的后台Task调用该Session的Delegate中的URLSession:downloadTask:didFinishDownloadingToURL:(成功的话)和URLSession:task:didCompleteWithError:(成功或者失败都会调用)方法作处理,以上的回调代码块能够在这里调用。
NSURLSession学习笔记(二)Session Task
Session Task分为三种Data Task,Upload Task,Download Task。毫无疑问,Session Task是整个NSURLSession架构的核心目标。
下面写了一个简单的Demo来初步使用下三种任务对象。这里使用的是convenience methods,并无定制session和使用协议,都是采用completionHandler做为回调动做。
故事板内容为:

第一种Data Task用于加载数据,使用全局的shared session和dataTaskWithRequest:completionHandler:方法建立。代码以下:
- - (IBAction)loadData:(id)sender {
-
- [self.spinner startAnimating];
-
-
- NSURL *url = [NSURL URLWithString:@"http://blog.csdn.net/u010962810"];
- NSURLRequest *request = [NSURLRequest requestWithURL:url];
- NSURLSession *session = [NSURLSession sharedSession];
- NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
- completionHandler:
- ^(NSData *data, NSURLResponse *response, NSError *error) {
-
- [self showResponseCode:response];
-
-
- [self.webView loadData:data
- MIMEType:@"text/html"
- textEncodingName:@"utf-8"
- baseURL:nil];
-
-
- [self.spinner stopAnimating];
- }];
-
- [dataTask resume];
- }
-
- - (void)showResponseCode:(NSURLResponse *)response {
- NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
- NSInteger responseStatusCode = [httpResponse statusCode];
- NSLog(@"%d", responseStatusCode);
- }
completionHandler指定任务完成后的动做。注意必定要使用resume方法启动任务。(Upload Task和Download Task同理)
运行结果:

第二种Upload Task用于完成上传文件任务,使用方法相似:
- - (IBAction)uploadFile:(id)sender {
- }
第三种Download Task用于完成下载文件的任务,使用全局的shared session和downloadTaskWithRequest:completionHandler:方法建立。
注意:在下载任务完成后,下载的文件位于tmp目录下,由代码块中的location指定(不妨输出看看),咱们必需要在completion handler中将文件放到持久化的目录下保存。代码以下:
- - (IBAction)downloadFile:(id)sender {
- [self.spinner startAnimating];
-
- NSURL *URL = [NSURL URLWithString:@"http://b.hiphotos.baidu.com/image/w%3D2048/sign=6be5fc5f718da9774e2f812b8469f919/8b13632762d0f703b0faaab00afa513d2697c515.jpg"];
- NSURLRequest *request = [NSURLRequest requestWithURL:URL];
- NSURLSession *session = [NSURLSession sharedSession];
- NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
- completionHandler:
- ^(NSURL *location, NSURLResponse *response, NSError *error) {
- [self showResponseCode:response];
-
-
- NSLog(@"%@", location);
-
-
- NSString *documentsPath = [self getDocumentsPath];
- NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];
- NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];
-
-
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if ([fileManager fileExistsAtPath:[fileURL path] isDirectory:NULL]) {
- [fileManager removeItemAtURL:fileURL error:NULL];
- }
- [fileManager moveItemAtURL:location toURL:fileURL error:NULL];
-
-
- NSURLRequest *showImage_request = [NSURLRequest requestWithURL:fileURL];
- [self.webView loadRequest:showImage_request];
-
- [self.spinner stopAnimating];
- }];
-
- [downloadTask resume];
- }
-
- - (NSString *)getDocumentsPath {
- NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
- NSString *documentsPath = documents[0];
- return documentsPath;
- }
运行结果:

这个Demo中没有为NSURLSession指定session的delegate,因此没有使用委托中的方法,功能比较有限,并且也没有自行定制session的配置,因此只能执行简单的任务,可是对于加载数据,下载一张图片等任务已经能够应付自如。对于建立后台下载任务,支持断点续传的下载任务等将在下一篇文章中分析介绍。
NSURLSession学习笔记(三)Download Task
NSURLSession的Download Task用于完成下载任务,本文介绍如何建立断点续传的下载任务和后台下载任务。
咱们直接从分析Demo入手:
故事板以下:

只有一个View Controller,用于建立各类下载任务,并将下载后的图片显示到视图上,下载过程当中会更新下载进度。
头文件代码以下:
- #import <UIKit/UIKit.h>
-
- @interface ViewController : UIViewController <NSURLSessionDownloadDelegate>
-
- @property (strong, nonatomic) NSURLSession *currentSession;
- @property (strong, nonatomic, readonly) NSURLSession *backgroundSession;
-
- @property (strong, nonatomic) NSURLSessionDownloadTask *cancellableTask;
- @property (strong, nonatomic) NSURLSessionDownloadTask *resumableTask;
- @property (strong, nonatomic) NSURLSessionDownloadTask *backgroundTask;
-
- @property (strong, nonatomic) NSData *partialData;
-
- @property (weak, nonatomic) IBOutlet UIImageView *downloadedImageView;
-
- @property (weak, nonatomic) IBOutlet UILabel *currentProgress_label;
- @property (weak, nonatomic) IBOutlet UIProgressView *downloadingProgressView;
-
- @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancellableDownload_barButtonItem;
- @property (weak, nonatomic) IBOutlet UIBarButtonItem *resumableDownload_barButtonItem;
- @property (weak, nonatomic) IBOutlet UIBarButtonItem *backgroundDownload_barButtonItem;
- @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelTask_barButtonItem;
-
- - (IBAction)cancellableDownload:(id)sender;
- - (IBAction)resumableDownload:(id)sender;
- - (IBAction)backgroundDownload:(id)sender;
- - (IBAction)cancelDownloadTask:(id)sender;
-
- @end
1、建立普通的下载任务
这种下载任务是能够取消的,代码以下:
- - (IBAction)cancellableDownload:(id)sender {
- if (!self.cancellableTask) {
- if (!self.currentSession) {
- [self createCurrentSession];
- }
-
- NSString *imageURLStr = @"http://farm6.staticflickr.com/5505/9824098016_0e28a047c2_b_d.jpg";
- NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
- self.cancellableTask = [self.currentSession downloadTaskWithRequest:request];
-
- [self setDownloadButtonsWithEnabled:NO];
- self.downloadedImageView.image = nil;
-
- [self.cancellableTask resume];
- }
- }
若是当前的session为空,首先须要建立一个session(该session使用默认配置模式,其delegate为本身):
- - (void)createCurrentSession {
- NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
- self.currentSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:nil];
- self.currentSession.sessionDescription = kCurrentSession;
- }
随后建立下载任务并启动。
这种任务是可取消的,即下次下载又从0.0%开始:
- if (self.cancellableTask) {
- [self.cancellableTask cancel];
- self.cancellableTask = nil;
- }
2、建立可恢复的下载任务
可恢复的下载任务支持断点续传,也就是若是暂停当前任务,在下次再执行任务时,将从以前的下载进度中继续进行。所以咱们首先须要一个NSData对象来保存已经下载的数据:
- @property (strong, nonatomic) NSData *partialData;
执行下载任务时,若是是恢复下载,那么就使用downloadTaskWithResumeData:方法根据partialData继续下载。代码以下:
- - (IBAction)resumableDownload:(id)sender {
- if (!self.resumableTask) {
- if (!self.currentSession) {
- [self createCurrentSession];
- }
-
- if (self.partialData) {
- self.resumableTask = [self.currentSession downloadTaskWithResumeData:self.partialData];
- }
- else {
- NSString *imageURLStr = @"http://farm3.staticflickr.com/2846/9823925914_78cd653ac9_b_d.jpg";
- NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
- self.resumableTask = [self.currentSession downloadTaskWithRequest:request];
- }
-
- [self setDownloadButtonsWithEnabled:NO];
- self.downloadedImageView.image = nil;
-
- [self.resumableTask resume];
- }
- }
在取消下载任务时,要将partialData数据保存起来,并且不要调用cancel方法:
- else if (self.resumableTask) {
- [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData) {
-
- self.partialData = resumeData;
- self.resumableTask = nil;
- }];
- }
另外在恢复下载时,NSURLSessionDownloadDelegate中的如下方法将被调用:
- - (void)URLSession:(NSURLSession *)session
- downloadTask:(NSURLSessionDownloadTask *)downloadTask
- didResumeAtOffset:(int64_t)fileOffset
- expectedTotalBytes:(int64_t)expectedTotalBytes {
- NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);
- }
3、建立后台下载任务
后台下载任务,顾名思义,当程序进入后台后,下载任务依然继续执行。
首先建立一个后台session单例,这里的Session配置使用后台配置模式,使用backgroundSessinConfiguration:方法配置时应该经过后面的参数为该后台进程指定一个标识符,在有多个后台下载任务时这个标识符就起做用了。
- - (NSURLSession *)backgroundSession {
- static NSURLSession *backgroundSess = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundSessionID];
- backgroundSess = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
- backgroundSess.sessionDescription = kBackgroundSession;
- });
-
- return backgroundSess;
- }
在建立后台下载任务时,应该使用后台session建立,而后resume。
- - (IBAction)backgroundDownload:(id)sender {
- NSString *imageURLStr = @"http://farm3.staticflickr.com/2831/9823890176_82b4165653_b_d.jpg";
- NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
- self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request];
-
- [self setDownloadButtonsWithEnabled:NO];
- self.downloadedImageView.image = nil;
-
- [self.backgroundTask resume];
- }
在程序进入后台后,若是下载任务完成,程序委托中的对应方法将被回调:
- - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
- NSLog(@"Application Delegate: Background download task finished");
-
-
- self.backgroundURLSessionCompletionHandler = completionHandler;
- }
而后调用NSURLSessionDownloadDelegate中的方法:
如下是
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL*)location中的方法,该方法只有下载成功才被调用:
- else if (session == self.backgroundSession) {
- self.backgroundTask = nil;
- AppDelegate *appDelegate = [AppDelegate sharedDelegate];
- if (appDelegate.backgroundURLSessionCompletionHandler) {
-
- void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
- appDelegate.backgroundURLSessionCompletionHandler = nil;
- handler();
- }
- }
另外不管下载成功与否,如下方法都会被调用:
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
- NSLog(@"NSURLSessionDownloadDelegate: Complete task");
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [self setDownloadButtonsWithEnabled:YES];
- });
-
- if (error) {
- NSLog(@"下载失败:%@", error);
- [self setDownloadProgress:0.0];
- self.downloadedImageView.image = nil;
- }
- }
取消后台下载任务时直接cancel便可:
- else if (self.backgroundTask) {
- [self.backgroundTask cancel];
- self.backgroundTask = nil;
- }
4、NSURLSessionDownloadDelegate
为了实现下载进度的显示,须要在委托中的如下方法中实现:
- - (void)URLSession:(NSURLSession *)session
- downloadTask:(NSURLSessionDownloadTask *)downloadTask
- didWriteData:(int64_t)bytesWritten
- totalBytesWritten:(int64_t)totalBytesWritten
- totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
- {
-
- double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;
- [self setDownloadProgress:downloadProgress];
- }
-
- - (void)setDownloadProgress:(double)progress {
- NSString *progressStr = [NSString stringWithFormat:@"%.1f", progress * 100];
- progressStr = [progressStr stringByAppendingString:@"%"];
-
- dispatch_async(dispatch_get_main_queue(), ^{
- self.downloadingProgressView.progress = progress;
- self.currentProgress_label.text = progressStr;
- });
- }
从已经保存的数据中恢复下载任务的委托方法,fileOffset指定了恢复下载时的文件位移字节数:
- - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
- didResumeAtOffset:(int64_t)fileOffset
- expectedTotalBytes:(int64_t)expectedTotalBytes;
只有下载成功才调用的委托方法,在该方法中应该将下载成功后的文件移动到咱们想要的目标路径:
- - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
- didFinishDownloadingToURL:(NSURL *)location;
不管下载成功或失败都会调用的方法,相似于try-catch-finally中的finally语句块的执行。若是下载成功,那么error参数的值为nil,不然下载失败,能够经过该参数查看出错信息:
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
- didCompleteWithError:(NSError *)error;
后台下载的运行结果:
启动任务后,进入后台:

下载完成后,控制台将会“通知”咱们:
- 2014-02-05 18:30:39.767 DownloadTask[3472:70b] Application Delegate: App did become active
- 2014-02-05 18:30:43.734 DownloadTask[3472:70b] Application Delegate: App will resign active
- 2014-02-05 18:30:43.735 DownloadTask[3472:70b] Application Delegate: App did enter background
- 2014-02-05 18:30:45.282 DownloadTask[3472:70b] Application Delegate: Background download task finished
- 2014-02-05 18:30:45.285 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Finish downloading
- 2014-02-05 18:30:45.301 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Complete task
再次启动程序,能够看到加载好的页面:

能够看到,经过后台下载让咱们的程序更加异步地运行。NSURLSession封装了对应的接口,让咱们要执行的任务更加专门化,这个新的网络架构的功能真的很强大。
本文的Demo基于https://github.com/ShinobiControls/iOS7-day-by-day改写,内容基本一致。
原来的Demo也有一篇博客对应:iOS7 Day-by-Day :: Day 1 :: NSURLSession。
本文的Demo也已经上传,有兴趣的话能够下载看看。