基本上所有的应用都需要用到网络,Apple 给我们提供的网络交互框架主要是基于 URLSession 的 URL 加载系统。

URLSession介绍

URLSession 实际指代 Foundation 框架的 URL 加载系统中的一些类和协议。主要由以下几个类组成:

7feb1b43-1d81-4fdf-ae75-baa10957fbb8
  • URLSession

    • 负责请求/响应的关键对象,可以理解成和服务端的一个会话,必须创建会话,在会话的基础上进行网络数据的上传和获取,使用 URLSessionConfiguration 配置对象进行创建。
    • 在请求/响应的执行过程中调用 URLSessionTaskDelegate 定义的各种代理方法。
  • URLSessionConfiguration

    • 用于对 URLSession 对象进行初始化,可以配置 可用网络Cookie安全性缓存策略自定义协议启动事件 等选项,以及用于移动设备优化的相关选项。
    • 几乎可以配置任何网络相关选项。
  • URLSessionTask

    • 一个抽象类,其子类可以创建不同类型的任务(Task),如:下载、上传、获取数据(如:JSON 或 XML)。
    • 在特定 Session 中执行。
  • URLRequest

    • 将 URL 和请求协议相关的属性封装起来,传递给 URLSession 用于创建URLSessionTask。
  • URLResponse

    • 将返回的元数据和内容数据本身封装起来。

在 apple 官方文档中,将网络模块分为了以下几种类:

URLSession

在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据 缓存策略(Cache Policy) 以及 可用性(availability) 的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应,以便将来的请求可以使用。

在一个请求被发送到服务器过程中,服务器可能会发出 鉴权查询(Authorization Challenge),这可以由共享的 Cookie 或 证书存储(Credential Storage) 来自动响应,或者由被委托对象来响应。此外,发送中的请求也可以被注册的 URLProtocol 对象所拦截,以便在必要时改变其加载行为。

URLSession 是负责请求/响应的关键对象,URLSession 本身并不会进行请求,而是通过创建 Task 的形式来进行网络请求。同一个 URLSession 可以创建多个 Task,并且这些 Task 之间的 Cache 和 Cookie 是共享的。

URLSession 在管理请求/响应的过程中会调用相关的代理方法。这些代理方法主要分两类:

  • Session 的委托对象实现的代理方法(URLSessionDelegate 定义的方法)
    • 主要用于处理连接层问题,如:服务器信任、客户端证书认证、NTLM 和 Kerberos 协议等问题
  • Task 的委托对象实现的代理方法(URLSessionTaskDelegate 及其子协议定义的方法)
    • 主要用于处理以网络请求为基础的问题,如:Basic,Digest,代理身份验证(Proxy Authentication) 等。

URLSessionTask

URLSessionTask 是一个抽象类,其包含如下 5 个实体子类。这 5 个子类封装了 5 个最基本的网络任务:获取数据(如:JSON 或 XML)、上传文件下载文件TCP/IP连接(iOS9),以及 websocket 长连接(iOS13)。

image-20210621162449711

上图所示为这些类之间的继承关系。对于 URLSessionDataTask,服务器会有响应数据;而对于上传请求,服务器也会有响应数据,所以 URLSessionUploadTask 继承自 URLSessionDataTaskURLSessionDownloadTask 完成时,会带回已下载文件的一个临时的文件路径,URLSessionStreamTask是 iOS9新加入的类,通过这个类可以直接使用socket链接,读取输入流和写入输出流等。URLSessionWebSocketTask是 iOS13新加入的类,通过类名很容易联想到是用于 websocket 连接的类,使用的时候需要使用 webSocket 的链接(ws/wss)。

task共有的控制方法:

public func cancel()
public func suspend()
public func resume()

task 本身是有状态的,枚举 NSURLSessionTaskState 列出了这些状态:

@available(iOS 7.0, *)
public enum NSURLSessionTaskState : Int {
	case Running /* The task is currently being serviced by the session */
    case Suspended
    case Canceling /* The task has been told to cancel.  The session will receive a URLSession:task:didCompleteWithError: message. */
    case Completed /* The task has completed and the session will receive no more delegate notifications */
}

这些很好理解,任务有运行状态,暂停状态,正在取消状态,完成状态。事实上,task 被创建后,就会处于Suspended暂停状态,所以当设置好 task 后,需要调用 resume() 来启动这个task。调用suspend()cancel()则分别暂停和取消 task。

关于 URLSessionTask 的数据返回方式,主要有两种方式:

  • completionHandler 回调
  • URLSessionDelegate 代理

值得注意的是,URLSessionTask 及其子类都有着各自的代理协议,它们之间也存在着继承关系,具体的方法可以查阅官方文档

  • URLSessionDelegate:定义了网络请求最基础的代理方法。作为所有代理的基类。
  • URLSessionTaskDelegate:定义了网络请求任务相关的代理方法。
  • URLSessionDownloadDelegate:定义了下载任务相关的代理方法,如:下载进度等。
  • URLSessionDataDelegate:定义了普通数据任务和上传任务相关的代理方法。
  • URLSessionStreamDelegate: 定义了写入和读取流数据等代理方法。
  • URLSessionWebSocketDelegate: 定义了 WebSocket 建立连接和失去连接的代理。

URLSessionConfiguration

URLSessionConfiguration 对象用于对 URLSession 进行初始化。

URLSessionConfiguration提供给开发者相当大的灵活性和控制权。从指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,使用 URLSessionConfiguration 可以找到几乎任何想要进行配置的选项。

URLSession 在初始化时会把配置它的 URLSessionConfiguration 对象进行一次深拷贝,并保存到自己的 configuration 属性中,这个属性是只读的。也就是说,configuration 只在初始化时被读取一次,之后都是不会变化的。

URLSessionConfiguration 有三个类初始化方法:

  • + defaultSessionConfiguration
    • 返回一个标准的配置,具有共享 HTTPCookieStorage、共享 URLCache、共享 URLCredentialStorage
  • + ephemeralSessionConfiguration
    • 返回一个预设的配置,该配置中不会对缓存、Cookie和证书进行持久性存储。这对于实现类似无痕浏览这种功能来说是很理想的。
  • + backgroundSessionConfiguration:(NSString *)identifier
    • 创建一个后台 Session。后台 Session 不同于普通 Session,后台 Session 可以在应用程序挂起、退出或崩溃的情况下进行上传/下载任务。初始化时指定的标识符,可用于向任何可能在进程外恢复后台传输的 守护进程(daemon) 提供上下文。

常用配置

HTTPAdditionalHeaders 为基于 configuration 的 Session 生成的所有 Task 中的 NSRULRequest 对象添加额外的请求头部字段。默认为空。

URLSession 默认为 URLRequest 对象添加了如下请求头部字段:

  • Authorization
  • Connection
  • Host
  • Proxy-Authenticate
  • Proxy-Authorization
  • WWW-Authenticate

如果在 HTTPAdditionalHeaders 自定义的头部字段与 URLRequest 对象重复了,则优先使用 URLRequest 对象中的请求头部字段。

利用 HTTPAddtionalHeaders 可以添加如下这些请求头部字段:

  • Accept
  • Accept-Language
  • User-Agent
    open var networkServiceType: NSURLRequest.NetworkServiceType  // 指定网络请求的类型,枚举
    
    open var allowsCellularAccess: Bool				// 是否使用蜂窝网络
        
	  var requestCachePolicy: NSURLRequest.CachePolicy	// 缓存策略
    
    open var timeoutIntervalForRequest: TimeInterval  // 请求超时时间。默认60s
    
    open var timeoutIntervalForResource: TimeInterval  // 基于此配置的会员所有资源下载的超时时间,默认7天

URL Loading System 提供了 app 级别的 cookie 存储机制。URL Loading System 中涉及到 cookie 操作的两个类分别是:

  • HTTPCookieStorage:这个类提供了管理 cookie 存储的功能。默认情况下会使用 HTTPCookieStorage+ sharedHTTPCookieStorage 单例。
  • HTTPCookie:用来封装 cookie 数据和属性的类。

URLRequest 提供了 HTTPShouldHandleCookies 属性来设置请求发起时,是否需要 cookie manager 自动处理 cookie。

安全策略

有些服务器会对某些特定的内容限制访问权限,只对提供了信任证书通过认证的用户提供访问资格。对于 web 服务器来说,受保护的内容被聚集到一个需要凭证才能访问的区域。在客户端上,有时也需要根据凭证来确定是否信任要访问的服务器。

URL Loading System 提供了封装凭证(credentials)、封装保护区域(protected areas)和保存安全凭证(secure credential)的类:

  • URLCredential:封装一个含有认证信息(比如用户名和密码等)和持久化存储行为的凭证(credential)。
  • URLProtectionSpace:服务器上某个需要凭证才能访问的区域。
  • URLCredentialStorage:管理凭证的存储以及 URLCredential 和相应的 URLProtectionSpace 之间的映射关系。
  • URLAuthenticationChallenge:在客户端向有限制访问权限的服务器发起请求时,服务器会询问凭证信息,包括凭证、保护空间、认证错误信息、认证响应等。这个类会将这些信息封装起来。URLAuthenticationChallenge 实例通常被 URLProtocol 子类用来通知 URL Loading System 需要认证,以及在 URLSession 的代理方法中用来处理认证。

Cache 管理

URL Loading System 提供了 app 级别的 HTTP 响应缓存,在使用 NSURLSession 发起请求时,我们可以通过设置 URLRequestURLSessionConfiguration 的缓存策略(cache policy)来决定是否缓存以及如何处理缓存。同时,我们还可以通过实现 URLSession:dataTask:willCacheResponse:completionHandler: 方法来针对特定的 URL 设置缓存策略。

实际上,不是所有请求的响应都能被缓存起来,URL Loading System 目前只支持对 http 和 https 请求的响应进行缓存。

URL Loading System 中提供的管理缓存的类有以下两个:

  • NSURLCache:通过这个类可以设置缓存大小和位置,以及读取和存储各个请求的 NSCachedURLResponse
  • NSCachedURLResponse:封装了请求元数据(一个 NSURLResponse 对象)和实际响应内容(一个 NSData 对象)。

协议支持

URL Loading System 本身只支持 http、https、file、ftp 和 data 协议。URLProtocol 是一个抽象类,提供了处理 URL 加载的基础设施。通过实现自定义的 URLProtocol 子类,可以让我们的 app 支持自定义的数据传输协议。

借助URLProtocol,我们不必改动应用在网络调用上的其他部分,就可以改变 URL 加载行为的全部细节。运用这一点,我们可以自由发挥,做很多想做的事情,比如:

总结

作为 iOS 原生的网络框架,我们的应用中的所有网络交互几乎都基于 URLSession ,所以理解并熟练掌握 URLSession 的使用非常有必要,既可以学习到苹果对于网络的一些设计,也能对我们学习一些三方库的原理有很大帮助。

相关文档:

NSURLSession | Apple Developer Documentation