简介
NSURLSession
是苹果公司在2013的 WWDC大会上随 iOS7 一起发布的,是 NSURLConnection
的继任者。 NSURLConnection
在iOS9 之后也被宣布弃用。目前 AFNetworking、SDWebImage
等知名类库也都更新使用了 NSURLSession 。
NSURLSession的优势:
- 针对不同的网络请求任务提供了专门的解决方案;
- 支持任务取消、暂停、恢复、支持应用程序后台下载;
- 安全身份验证方案;
- 下载操作内存优化;
- 更灵活请求的配置。
使用
首先我们看下NSURLSession最简单的使用,通过下面代码就可以实现了基本的网络请求了。
NSURL *url = [NSURL URLWithString:@"http://....."];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// handle response
}];
[task resume];
所有的NSURLSession 网络请求基本遵循以下几个步骤:
- 获取NSURLSession对象
- 通过session对象创建 task 任务
- 执行 task(task 默认是挂起的,通过resume执行)
NSURLSession
NSURLSession
继承NSObject
,提供了3种会话模式,主要通过 NSURLSessionConfiguration
来进行初始化,同时还可以设置 dalegate
来进行下载过程监听
+ (NSURLSession *)sharedSession; //共享的会话,该会话使用全局的Cache,Cookie和证书,无法进行 Configuration配置
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration; // 通过 NSURLSessionConfiguration初始化
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue; // 通过 NSURLSessionConfiguration、 delegate 、队列初始化
接下来我们看下NSURLSessionConfiguration的3种配置,他们是和NSURLSession的会话模式一一对应的
@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
-
defaultSessionConfiguration
默认配置使用的是持久化的硬盘缓存,存储证书到用户钥匙链。存储cookie到shareCookie。 -
ephemeralSessionConfiguration
这个配置中不会对缓存,Cookie 和证书进行持久性的存储。 -
+ backgroundSessionConfigurationWithIdentifier:(NSString *)identifier
它会返回一个后台 session。可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。
另外: NSURLSessionConfiguration是提供了很多属性,从 指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,可以找到几乎任何你想要进行配置的选项。
了解了 NSURLSession
的模式以及配置,接下来我们看一下 NSURLSessionTask
NSURLSessionTask
NSURLsessionTask
是一个抽象类,其下有 3 个实体子类可以直使用用:NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
。这 3 个子类封装了程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。
-
NSURLSessionDataTask
可以用来处理一般的网络请求 -
NSURLSessionDownloadTask
主要用于处理下载请求。 -
NSURLSessionUploadTask
用于处理上传请求。
当一个 NSURLSessionDataTask
完成时,它会带有相关联的数据,而一个 NSURLSessionDownloadTask
任务结束时,它会带回已下载文件的一个临时的文件路径。因为一般来说,服务端对于一个上传任务的响应也会有相关数据返回,所以 NSURLSessionUploadTask
继承自 NSURLSessionDataTask
。
所有的task
都是可以取消,暂停或者恢复的。当一个 download task
取消时,可以通过选项来创建一个恢复数据(resume data),然后可以传递给下一次新创建的 download task,以便继续之前的下载。
不同于直接使用alloc-init
初始化方法,task
是由一个 NSURLSession
创建的。每个 task 的构造方法都对应有或者没有 completionHandler
block 的两个版本。如果 使用completionHandler
block,代理回调将不会执行。
NSURLSessionTask工厂方法
NSURLSessionTask
的工厂方法可以根据我们不同的需求返回不同的 task
,这些task
不会立即运行,允许我们进一步的配置,然后可以使用 resume
方法来让它开始运行。
Datatask
可以通过 NSURL
或 NSURLRequest
创建
NSURL *URL = [NSURL URLWithString:@"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
[task resume];
Uploadtask
的创建需要使用一个 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];
Download task
也需要一个 request或者Url,不同之处在于 completionHandler
这个 block。Datatask
和 uploadtask
会在任务完成时一次性返回,但是 Download task
是将数据一点点地写入本地的临时文件。所以在 completionHandler 这个 block 里,我们需要把文件从一个临时地址移动到一个永久的地址保存起来:
NSURL *URL = [NSURL URLWithString:@"http://example.com/file.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
completionHandler: ^(NSURL *location, NSURLResponse *response, NSError *error) {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];
NSURL *newFileLocation = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];
[[NSFileManager defaultManager] copyItemAtURL:location toURL:newFileLocation error:nil];
}];
[downloadTask resume];
上面介绍了几种 NSURLSessionTask 的基本使用,都是通过completionHandler
这个 block 来进行任务处理回调的,下来我们看看他的代理都有哪些,以及使用
NSURLSession代理
在我们请求过程中,如果想要监听网络操作过程中发生的事件,比如我们下载一个大文件的时候,如果要等到下载完成可能会需要比较长的事件,这时候更好的体验是能够提供一个下载进度。类似这样的事件我们就需要用到代理。
我们在使用三种 task 的任意一种的时候都可以指定相应的代理。NSURLSession 的代理对象结构如下:
-
NSURLSessionDelegate
作为所有代理的基类,定义了网络请求最基础的代理方法。 -
NSURLSessionTaskDelegate
- 定义了网络请求任务相关的代理方法。 -
NSURLSessionDownloadDelegate
- 用于下载任务相关的代理方法,比如下载进度等等。 -
NSURLSessionDataDelegate
- 用于普通数据任务和上传任务。
下面以下载为例,我们看下代码如何实现的
- (void)downloadtask{
// 服务器地址
NSURL *url = [NSURL URLWithString:@"http://d1.music.126.net/dmusic/NeteaseMusic_1.5.10_632_web.dmg"];
// 创建 SessionConfiguration配置
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 创建session实例并设置代理,用于监听下载
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
// 创建 task
self.task = [session downloadTaskWithURL:url];
// 开始执行
[self.task resume];
}
#pragma -mark NSURLSessionDownloadDelegate
/* 当下载任务完成下载时调用*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
// location是一个temp文件下的临时路径,自己需要保存
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
}
/* 定期发送通知委托下载进度. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
// 可在这里通过已写入的长度和总长度算出下载进度
CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
NSLog(@"%f",progress);
}
/* 当下载已恢复时发送 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
}
/* 任务完成调用,查看是否成功 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
if (!error){
NSLog(@"下载完成");
}else{
NSLog(@"下载失败");
}
}
通过实现NSURLSessionDownloadDelegate 协议,就可以实现接收下载完成的通知,下载进度变化的通知,以及下载进度恢复的通知。
总结
NSURLSession
除了我们介绍的支持 task 特性,NSURLSessionConfiguration 配置对象,以及代理之外还提供了很多关于网络请求的相关特性,比如缓存控制,Cookie 控制,HTTP 验证操作等等。总之 NSURLSession 简单的接口之外,也提供了强大的体系。
NSURLSession
相比 AFN
这些第三方库来说也有一些不足,比如它没有提供很方便的自动数据类型转换。比如,AFN 中可以自动将服务端返回的 JSON 数据识别并解析出来,而使用 NSURLSession
则需要自己来完成。
后续有时间我会继续分析一下 NSURLSession在 AFN 中的使用!
参考链接:
https://developer.apple.com/documentation/foundation/nsurlsession
https://swiftcafe.io/2015/12/20/nsurlsession
http://www.cocoachina.com/ios/20180108/21778.html