NSURLSession

为什么要使用 NSURLSession

在 iOS 7 中,Apple 发布了 NSURLSession 来作为 NSURLConnection 的替代品,此类为网络的推荐方法。它给我们带来了一对新的特性:

  • 后台上传和下载:当 NSURLSession 被创建的时候会有一个配置选项,你可以获得后台网络的所有特性。这有益于电池寿命,支持 UIKit 多任务处理并且在相同进程内传输时使用同一个委托模式。
  • 暂停和恢复网络操作的能力:使用 NSURLSession API 的任何网络任务都能够暂停、停止和重新开始。再也没有子类化 NSOperation 的必要了。
  • 可配置的容器:对于放入的网络请求来说,每一个 NSURLSession 都是一个可配置的容器。例如,如果你需要设置 HTTP 头部选项,你只需要设置一次然后 session 中的每一个 request 都将有着相同的配置。
  • 子类化和私有存储:NSURLSession 具有衍生性,你可以在每个基础 session 上使用私有存储。这允许你在全局状态的范围外拥有私有存储对象。
  • 改进的授权处理:授权是在一个特殊的连接基础上完成的。当使用 NSURLConnection 时,如果授权出了问题会返回一个任意请求,而你根本不能确切的知道是哪一个 request 导致的问题。有了 NSURLSession,它的委托会处理这些。
  • 丰富的委托模型:NSURLConnection 拥有一些异步 block 方法,然而一个委托不能用在这些方法中。当需要授权时,request 创建后,它可能有效也可能无效。有了 NSURLSession,你可以使用基于异步的 block 语句然后设置一个委托来处理授权。
  • 通过文件系统来上传和下载:这鼓励数据(文件内容)和元数据(URL 和设置)分离。

NSURLConnection VS NSURLSession

假如要获取 London 的天气数据。

数据 URL 为:

NSString *londonWeatherUrl = @"http://api.openweathermap.org/data/2.5/weather?q=London,uk";  

如果使用 NSURLConnection:

NSURLRequest *request = [NSURLRequest requestWithURL: [NSURL URLWithString:londonWeatherUrl]];  
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response,
NSData *data,  
NSError *connectionError) { // handle response  
}];

如果使用 NSURLSession:

NSURLSession *session = [NSURLSession sharedSession];  
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    // handle response
}] resume];

如此看来 NSURLSession 要简单许多。

NSURLSession Class

下图是大致流程:

2014-04-30/01-05-11

NSURLSessionConfiguration

有三种方式来创建 NSURLSessionConfiguration:

  • defaultSessionConfiguration - 创建一个使用全局 cache,cookie 和凭证存储的 configuration 对象。这种配置会使你的 session 看起来很像 NSURLConnection。
  • ephemeralSessionConfiguration - 这种配置是针对「隐私」会话并且对于 cache,cookie 或者凭证存储来说都不会有持久化存储。
  • backgroundSessionConfiguration - 这种配置用来在你想要创建远程推送通知或当 app 挂起时发送网络请求。

一旦你创建了 NSURLSessionConfiguration 那你就可以像下面这样设置许多属性:

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];  
// 1
sessionConfig.allowsCellularAccess = NO;  
// 2
[sessionConfig setHTTPAdditionalHeaders: @{@"Accept": @"application/json"}];
// 3
sessionConfig.timeoutIntervalForRequest = 30.0; sessionConfig.timeoutIntervalForResource = 60.0; sessionConfig.HTTPMaximumConnectionsPerHost = 1;  
  1. 这里表示限制网络操作只能使用 WIFI。
  2. 这将设置所有请求只接受JSON响应。
  3. 这些属性将会设置资源或请求的延时。你也可以限制你的 App 只能有一个对主机的网络连接。

NSURLSession

举个下载图片的例子:

// 1
NSString *imageUrl = @"http://www.raywenderlich.com/images/store/iOS7_PDFonly_280@2x_au thorTBA.png";  
// 2
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];  
// 3
NSURLSession *session =  
[NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
  1. 这段代码表示下载图片的 URL。
  2. 你总是要一开始就创建一个 NSURLSessionConfiguration。
  3. 这里使用当前类作为委托。

在创建好 Session 后,你就可以通过创建一个带有 completion handler block 对象的任务来下载图片了,如下:

// 1
NSURLSessionDownloadTask *getImageTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {  
// 2
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];  
//3
dispatch_async(dispatch_get_main_queue(), ^{ // do stuff with image _imageWithBlock.image = downloadedImage;  
}); }];
// 4
[getImageTask resume];
  1. 任务总是由 session 创建。这里创建了一个带有 block 对象的下载任务。你要记住你任然能使用 NSURLSessionDownloadDelegate 来跟踪下载进度。
  2. 这里使用了由 completion handler 提供的局部变量 来货的指向图片的指针。
  3. 最总你能够更新 UIImageView 的图片来显示新的了。
  4. 你总是要开始任务!

现在来看看不带有 completion handler block 的任务:

// 1
NSURLSessionDownloadTask *getImageTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl]];  
[getImageTask resume];
  1. 这段代码非常简洁,然而如果就这么做的话你不会看到任何事情发生。

你需要使用委托来实现一些 NSURLSessionDownloadDelegate 协议的方法。

首先,我们需要在下载完成的时候收到通知:

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location {
// use code above from completion handler
}

这段代码提供了最终下载完成的文件路径,并且你可以对这图片进行相应的操作。

如果你需要跟踪下载进度,对于每一个创建的任务,你需要使用下面的方法:

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWrittentotalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"%f / %f", (double)totalBytesWritten, (double)totalBytesExpectedToWrite);
}

NSURLSessionTask

2014-04-30/01-06-03

NSURLSessionDataTask

这个任务会发送 GET 请求从服务器拉取数据。返回的是 NSData 类型的数据。你需要把这转换为 XML,JSON,UIImage,plist 等类型的数据。例如:

NSURLSessionDataTask *jsonData = [session dataTaskWithURL:yourNSURLcompletionHandler:^(NSData *data, NSURLResponse *response,  
}];

NSURLSessionUploadTask

当你需要通过 POST 或者 PUT 命令来上传某些东西的时候使用此方法。上传一张图片的例子:

NSData *imageData = UIImageJPEGRepresentation(image, 0.6);  
NSURLSessionUploadTask *uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:imageData];  

这里的任务创建了一个 NSData 类型的数据上传任务。

NSURLSessionDownloadTask

NSURLSessionDownloadTask 可以超方便的从远程服务器下载文件并且能够在暂停后立马恢复下载。然而它的子类与其他的任务类型有些不同:

  • 这种类型的任务直接写入一个临时文件。
  • 在下载期间,session 会调用 URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpe ctedToWrite: 方法来更新状态信息。
  • 当任务结束时 URLSession:downloadTask:didFinishDownloadingToURL: 会被调用。
  • 当下载失败或者被取消时你用这个数据继续下载。

总结

上面讲的所有任务都是以挂起状态创建的。因此在创建后你需要调用 [uploadTask resume]; //上传任务的演示 来开始任务。

当你管理同一个会话中多个任务的时候,使用 taskIdentifier 属性能够让你独一无二的标识一个任务。