iOS 网络(1)——NSURLSession
最近公司针对刚入职的应届毕业生开展了一个的“新牛计划”,目的是让他们能够在一个月的时间内从零基础成长为 iOS 开发新手。
在这个过程中,我们需要承担讲师的角色。因此,我们对 iOS 开发的知识体系进行了划分,而我则负责讲解其中的 GCD 和网络相关部分。为此,我也算是学习了一下 iOS 开发所涉及到的一些网络知识,也学习了一些开源框架,包括:AFNetworking、YTKNetwork、CocoaAsyncSocket。这里,我首先对 NSURLSession 做一些相关总结。后续,将陆续贴出相关开源框架的学习心得。
NSURLSession 概述
WWDC 2013,苹果对基于 NSURLConnection
的 Foundation URL
加载系统进行了重构,推出了新一代基于 NSURLSession
的
Foundation URL 加载系统,并将其首先应用在了 iOS 7 和 Mac OS X 10.9
Mavericks 系统之中。
NSURLSession 架构
NSURLSession
这个名字,实际上是指代 Foundation 框架的
URL 加载系统中一些列相关的类和协议。上图所示为 NSURLSession
的系统架构图,主要由三个类构成:
NSURLSession
- 负责请求/响应的关键对象,使用
NSURLSessionConfiguration
配置对象进行创建。 - 在请求/响应的执行过程中调用
NSURLSessionTaskDelegate
所定义的各种代理方法。
- 负责请求/响应的关键对象,使用
NSURLSessionConfiguration
- 用于对
NSURLSession
对象进行初始化,可以配置 可用网络、Cookie、安全性、缓存策略、自定义协议、启动事件 等选项,以及用于移动设备优化的相关选项。 - 几乎可以配置任何选项。
- 用于对
NSURLSessionTask
- 一个抽象类,其子类可以创建不同类型的任务(Task),如:下载、上传、获取数据(如:JSON 或 XML)。
- 在特定 URL Session 中执行。
结合上述系统结构图,我们可以将 NSURLSession
中的类分为以下 6 种(如下图所示):
- URL 加载(URL Loading)
- 配置管理(Configuration Management)
- 缓存管理(Cache Policy)
- Cookie 存储(Cookie Storage)
- 认证和证书(Authentication and Credentials)
- 协议支持(Protocol Support)
在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据 缓存策略(Cache Policy) 以及 可用性(availability) 的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应,以便将来的请求可以使用。
在一个请求被发送到服务器过程中,服务器可能会发出
鉴权查询(Authorization Challenge),这可以由共享的
Cookie 或 证书存储(Credential Storage)
来自动响应,或者由被委托对象来响应。此外,发送中的请求也可以被注册的
NSURLProtocol
对象所拦截,以便在必要时改变其加载行为。
下面我们依次来详细介绍 URL 加载系统中的 3 个主要类:
NSURLSessionTask
、NSURLSession
、NSURLSessonConfiguration
。在
NSURLSessionConfiguration
中,我们将对缓存策略、Cookie
存储、自定义协议等内容稍作介绍。
NSURLSessionTask
NSURLSessionTask
是一个抽象类,其包含如下 3
个实体子类。这 3 个子类封装了 3
个最基本的网络任务:获取数据(如:JSON 或
XML)、上传文件、下载文件。
NSURLSessionDataTask
NSURLSessionUploadTask
NSURLSessionDownloadTask
上图所示为这些类之间的继承关系。对于
NSURLSessionDataTask
,服务器会有响应数据;而对于上传请求,服务器也会有响应数据,所以
NSURLSessionUploadTask
继承自
NSURLSessionDataTask
。NSURLSessionDownloadTask
完成时,会带回已下载文件的一个临时的文件路径。
关于 NSURLSessionTask
的数据返回方式,主要有两种方式:
completionHandler
回调NSURLSessionDelegate
代理
通过 completionHandler
回调将会创建一个隐式的代理(delegate),从而替代该 Task 原来的代理 ——
Session。
对于需要 override 原有 Session Task
的代理的默认行为的情况,我们需要使用不带 completionHandler
版本。
需要注意的是,NSURLSessionTask
及其子类都有着各自的代理协议,它们之间也存在着如下图所示的继承关系。
NSURLSessionDelegate
:定义了网络请求最基础的代理方法。作为所有代理的基类。NSURLSessionTaskDelegate
:定义了网络请求任务相关的代理方法。NSURLSessionDownloadDelegate
:定义了下载任务相关的代理方法,如:下载进度等NSURLSessionDataDelegate
:定义了普通数据任务和上传任务相关的代理方法。
下面简要介绍一下这三个子类。
NSURLSessionDataTask
NSURLSessionDataTask
主要用于
读取服务端的简单数据,如:JSON、XML 数据。
创建方法(基于 NSURLSession
对象)
1
2
3
4
5// 使用 NSURLRequest 对象创建
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
// 使用 NSURL 对象创建
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
CompletionHandler 1
2
3- (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;
NSURLSessionUploadTask
NSURLSessionUploadTask
主要用于
向服务器发送文件类型的数据。
创建方法(基于 NSURLSession
对象)
1
2
3
4
5
6
7// 使用 NSURLRequest 对象创建,上传时指定文件源
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
// 使用 NSURLRequest 对象创建,上传时指定数据源
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
CompletionHandler 1
2
3- (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;
NSURLSessionDownloadTask
NSURLSessionDownloadTask
主要用于
文件下载,它针对大文件的网络请求做了更多的处理,如:下载进度、断点续传等。
创建方法(基于 NSURLSession
对象)
1
2
3
4
5
6
7
8// 使用 NSURLRequest 对象创建
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
// 使用 NSURL 对象创建
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
// 使用之前已经下载的数据来创建
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
CompletionHandler 1
2
3
4
5- (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;
NSURLSession
NSURLSession
是负责请求/响应的关键对象,使用
NSURLSessionConfiguration
配置对象进行创建。
NSURLSession
本身并不会进行请求,而是通过创建 Task
的形式来进行网络请求。同一个 NSURLSession
可以创建多个
Task,并且这些 Task 之间的 Cache 和 Cookie 是共享的。
NSURLSession
在管理请求/响应的过程中会调用相关的代理方法。这些代理方法主要分两类:
- Session
的委托对象实现的代理方法(
NSURLSessionDelegate
定义的方法)- 主要用于处理连接层问题,如:服务器信任、客户端证书认证、NTLM 和 Kerberos 协议等问题
- Task
的委托对象实现的代理方法(
NSURLSessionTaskDelegate
及其子协议定义的方法)- 主要用于处理以网络请求为基础的问题,如:Basic,Digest,代理身份验证(Proxy Authentication) 等。
NSURLSessionConfiguration
NSURLSessionConfiguration
对象用于对
NSURLSession
进行初始化。
NSURLSessionConfiguration
对以前
NSMutableURLRequest
所提供的网络请求层的设置选项进行了扩充,提供给开发者相当大的灵活性和控制权。从指定可用网络,到
cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,可以发现使用
NSURLSessionConfiguration
可以找到几乎任何想要进行配置的选项。
NSURLSession
在初始化时会把配置它的
NSURLSessionConfiguration
对象进行一次深拷贝,并保存到自己的 configuration
属性中,而且这个属性是只读的。也就是说,configuration
只在初始化时被读取一次,之后都是不会变化的。
初始化
NSURLSessionConfiguration
有三个类工厂方法:
+ defaultSessionConfiguration
- 返回一个标准的配置,具有共享
NSHTTPCookieStorage
、共享NSURLCache
、共享NSURLCredentialStorage
。
- 返回一个标准的配置,具有共享
+ ephemeralSessionConfiguration
- 返回一个预设的配置,该配置中不会对缓存、Cookie和证书进行持久性存储。这对于实现类似秘密浏览这种功能来说是很理想的。
+ backgroundSessionConfiguration:(NSString *)identifier
- 创建一个后台 Session。后台 Session 不同于普通 Session,后台 Session 可以在应用程序挂起、退出或崩溃的情况下进行上传/下载任务。初始化时指定的标识符,可用于向任何可能在进程外恢复后台传输的 守护进程(daemon) 提供上下文。
属性配置
NSURLSessionConfiguration
拥有数十个配置属性。熟练掌握这些配置属性的用处,可以让应用程序充分地利用其网络环境。
常规配置
1 | @property(copy) NSDictionary *HTTPAdditionalHeaders |
HTTPAdditionalHeaders
为基于 configuration 的 Session
生成的所有 Task 中的 NSRULRequest
对象添加额外的请求头部字段。默认为空。
NSURLSession
默认为 NSURLRequest
对象添加了如下请求头部字段:
Authorization
Connection
Host
Proxy-Authenticate
Proxy-Authorization
WWW-Authenticate
如果在 HTTPAdditionalHeaders
自定义的头部字段与
NSURLRequest
对象重复了,则优先使用
NSURLRequest
对象中的请求头部字段。
利用 HTTPAddtionalHeaders
可以添加如下这些请求头部字段:
Accept
Accept-Language
User-Agent
- ...
1 | @property NSURLRequestNetworkServiceType networkServiceType |
指定网络传输类型。可以让操作系统快速响应,提高传输质量,延长电池寿命等。大多数应用程序都不需要设置。
1 | @property BOOL allowsCellularAccess |
是否使用蜂窝网络。默认是 YES
。
1 | @property NSTimeInterval timeoutIntervalForRequest |
指定请求的超时间隔。默认为 60s。
1 | @property NSTimeInterval timeoutIntervalForResource |
指定资源的超时间隔。默认是7天。
Cookie 策略
1 | @property(retain) NSHTTPCookieStorage *HTTPCookieStorage; |
存储了 Session 所使用的 Cookie。默认情况下会使用
NSHTTPCookieStorage
的
+ sharedHTTPCookieStorage
单例。
1 | @property BOOL HTTPShouldSetCookies; |
指定了请求是否应该使用 Session 存储的 Cookie,即
HTTPCookieStorage
属性的值。
1 | @property NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy; |
决定了什么情况下 Session 应该接受从服务器发出的 Cookie。
安全策略
1 | @property(retain) NSURLCredentialStorage *URLCredentialStorage; |
存储了 Session 所使用的证书。默认情况下会使用
NSURLCredentialStorage
的
+ sharedCredentialStorage
单例。
1 | @property SSLProtocol TLSMaximumSupportedProtocol; |
两者确定 Session 是否支持 SSL 协议。
缓存策略
1 | @property(retain) NSURLCache *URLCache; |
Session 使用的缓存。默认情况下会使用 NSURLCache
的
+ sharedURLCache
单例。
1 | @property NSURLRequestCachePolicy requestCachePolicy; |
指定了一个请求的缓存响应应该在什么时候返回。
后台传输
1 | @property(readonly, copy) NSString *identifier |
仅当使用 backgroundSessionConfigurationWithIdentifier:
方法创建配置对象时,才会设置此属性的值。identifier
唯一标识
后台会话 对象。
如果应用程序在后台任务进行传输时终止,可以使用
identifier
在应用程序重新启动时,重新创建
configuration
和 session
对象与
之前传输进行关联。
1 | @property BOOL sessionSendsLaunchEvents; |
设置传输结束时是否应该在后台恢复或启动应用程序。
1 | @property(getter=isDiscretionary) BOOL discretionary; |
设置后台 Task 是否可以由系统进行调度,从而获得最佳性能。
1 | @property BOOL shouldUseExtendedBackgroundIdleMode; |
设置应用程序切换至后台时是否保持打开 TCP 连接。
自定义协议
1 | @property(copy) NSArray<Class> *protocolClasses; |
用来配置特定某个 Session 所使用的自定义协议(该协议是
NSURLProtocol
的子类)的数组。
多路径 TCP
1 | @property NSURLSessionMultipathServiceType multipathServiceType; |
指定通过 Wi-Fi 和 蜂窝网络传输数据的多路径 TCP 的连接策略。
HTTP 策略与代理
1 | @property NSInteger HTTPMaximumConnectionsPerHost; |
用于限制连接到特定主机的数量。
1 | @property BOOL HTTPShouldUsePipelining; |
用于开启 HTTP 流水线(HTTP
pipelining),可以显着减少请求的加载时间,但是由于没有被服务器广泛支持,默认是
NO
的。
1 | @property(copy) NSDictionary *connectionProxyDictionary; |
指定了 Session 连接中的代理服务器
NSURLSession 使用
NSURLSession
的使用有如下几个步骤:
- 创建会话:基于
NSURLSessionConfiguration
对象创建NSURLSession
对象 - 创建任务:基于
NSURLSession
对象创建NSURLSessionTask
对象 - 执行任务:执行
NSURLSessionTask
对象
创建会话
会话的创建方式有三种: 1
2
3
4
5
6
7
8
9
10// 1. 直接创建,使用默认的 NSURLSessionConfiguration 配置
NSURLSession *session = [NSURLSession sharedSession];
// 2. 配置后创建,先初始化一个 NSURLSessionConfiguration 对象
[NSURLSession sessionWithConfiguration:defaultSessionConfiguration];
// 3. 设置加代理获得
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:[[NSOperationQueue alloc] init]];
创建任务
任务的创建在上文介绍 NSURLSessionTask
时已经提到。这里不做赘述。
执行任务
1 | // 执行任务 |
使用示例
GET 请求
1 | // 1. 创建会话 |
POST 请求
1 | // 1. 创建会话 |
结论
iOS 7 和 Mac OS X 10.9 Mavericks 中 URL 加载系统的变化,是对
NSURLConnection
进行深思熟虑后的一个自然而然的进化。尽管在这个体系结构中,某些决定对于可组合性和可扩展性而言是一种倒退,但是
NSURLSession
仍然是实现更高级别网络功能的一个强大的基础框架。
参考
- URL Loading System
- URL Loading System 概览
- iOS NSURLSession 详解
- URLSession Tutorial: Getting Started
- NSMutableURLRequest
- 从 NSURLConnection 到 NSURLSession
- From NSURLConnection to NSURLSession