URL Loading System

文章目录
  1. 1. 简介
  2. 2. 核心 URLSession & URLSessionTask
    1. 2.1. Session Configuration
    2. 2.2. session 网络请求
  3. 3. 实际使用
    1. 3.1. 从网站获取数据到内存
      1. 3.1.1. 使用回调处理接受结果:使用closure 回调得到 data
      2. 3.1.2. 接受请求详情&结果:使用 Delegate

简介

  • 在 Foundation 框架中
  • 支持协议:
    • FTP协议(ftp://)
    • 超文本传输协议(http://)
    • 加密超文本传输协议(https://)
    • 本地资源(file://)
    • 数据URLs(data://)
  • 关键类:URLSession,URL,URLRequest,Tasks,Data
  • 支持:Authorization credentials,cache 和cookies, 配置管理
  • 使用 closure 和 delegate 处理 Response

url_loading_system 核心类 分 6 个部分url_loading_system 核心类 分 6 个部分

核心 URLSession & URLSessionTask

一个 session 请求网络数据需要的核心内容

urlsessionurlsession

Session Configuration

每个配置属性都值得研究
configurationconfiguration
这篇文章有各个参数翻译

总共有三种 configuration

  • Default: 使用本地沙盒缓存,用户credential保存在 keychain 中
  • Ephemeral: 不使用沙盒,所有缓存credential 等数据存在 与 session 绑定的 RAM 中,session 失效后内存自动清理
  • Background: 跟 default session 相似,但是使用的是另外一个 进程(process) 处理数据传输,因为是跨进程,所以 background session 有些限制操作。(嗯后台session 使用了进程间通信)

为什么要有 configuration?
session 相当一个 manager,用来管理组织 task,网络事件处理的枢纽,configuration是这个枢纽的参数信息!相当于 一个 app 他都有 自己 preference

1
2
3
4
5
6
7
8
9
10
11
12
13
let configuration = URLSessionConfiguration.background(withIdentifier: "com.czw.backgroundDownload")
let configuration1 = URLSessionConfiguration.default// 允许用户拥有沙盒缓存器,当session 释放的时候,数据依然存在
let configuration2 = URLSessionConfiguration.ephemeral// session 无效时,东西就会消失

URLSession.init(configuration: configuration).dataTask(with: URL(string: urlDownloadStr)!)
.resume()// 后台 sessionConfiguration 不能设置 completeHandler,需要自己设置代理

print("background-内存大小:\(String(describing: configuration.urlCache.memoryCapacity))")
print("background-沙盒大小:\(String(describing: configuration.urlCache.diskCapacity))")
print("default-内存大小:\(String(describing: configuration1.urlCache.memoryCapacity))")
print("default-沙盒大小:\(String(describing: configuration1.urlCache.diskCapacity))")
print("ephemeral-内存大小:\(String(describing: configuration2.urlCache.memoryCapacity))")
print("ephemeral-沙盒大小:\(String(describing: configuration2.urlCache.diskCapacity))")

打印结果:
background-内存大小:nil
background-沙盒大小:nil
default-内存大小:512000
default-沙盒大小:10000000
ephemeral-内存大小:512000
ephemeral-沙盒大小:0

就像文档说的,当session 失效以后 ephemeral 的 cache 会消失,因为他没有沙盒空间

session 网络请求

urlsession request flowurlsession request flow

我们要做的

  1. 配置需要的 session configuration,
  2. 配置 session
    1. 系统提供的 shared 单例,系统自己配置 configuration,sessionDelegate,delegateQueue
    2. 自定义 session,自己设置 configuration,sessionDelegate,delegateQueue(是 serial queue)
  3. 提供 request & url 给 session
  4. 生成 task
    1. URLSessionTask
      • URLSessionDataTask
      • URLSessionUploadTask
      • URLSessionDownloadTask
      • URLSessionStreamTask
      • URLSessionWebSocketTask(iOS 13.0)
  5. 配置SessionDelegate, SessionTaskDelegate 对网络事件处理 (URLSessionTaskDelegate: URLSessionDelegate
    • URLSessionDataDelegate
    • URLSessionDownloadDelegate
    • URLSessionStreamDelegate
    • URLSessionWebSocketDelegate(iOS 13.0)

SessionDelegate 只有3个接口

1
2
3
4
5
6
7
8
@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?)

@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

@available(iOS 7.0, *)
optional func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)

至于各个 taskDelegate,对应不同的task 类型有个字不同协议接口
这里主要看 SessionTaskDelegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public protocol URLSessionTaskDelegate : URLSessionDelegate { 
@available(iOS 11.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void)

@available(iOS 11.0, *)
optional func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask)

@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void)

@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)

@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)

@available(iOS 10.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics)

@available(iOS 7.0, *)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
}

常用的是 dataTask,downloadTask,uploadTask

实际使用

从网站获取数据到内存

使用回调处理接受结果:使用closure 回调得到 data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// task 的状态
extension URLSessionTask {
@available(iOS 7.0, *)
public enum State : Int {
case running
case suspended // 初试状态
case canceling
case completed
}
}

func startLoad() {
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
if let mimeType = httpResponse.mimeType, mimeType == "text/html",
let data = data,
let string = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
self.webView.loadHTMLString(string, baseURL: url)
}
}
}

// session create 出来的 task 是 suspend 的状态
task.resume()
}

注意点:

  1. session create 出来的 task 初始状态是 suspend
  2. 回调处理是在 serial queue 任务放到子线程处理的,通知 UI 变化应切换主线程

接受请求详情&结果:使用 Delegate

这个时候 session 不应该使用 shared,而是要自己配置

1
2
3
4
5
6
7
private lazy var session: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.waitsForConnectivity = true
return URLSession(configuration: configuration,
delegate: self,
delegateQueue: nil)
}()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var receivedData: Data?

func startLoad() {
loadButton.isEnabled = false
let url = URL(string: "https://www.example.com/")!
receivedData = Data()
let task = session.dataTask(with: url)
task.resume()
}

// delegate methods

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
guard let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode),
let mimeType = response.mimeType,
mimeType == "text/html" else {
completionHandler(.cancel)
return
}
completionHandler(.allow)
}

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.receivedData?.append(data)
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
DispatchQueue.main.async {
self.loadButton.isEnabled = true
if let error = error {
handleClientError(error)
} else if let receivedData = self.receivedData,
let string = String(data: receivedData, encoding: .utf8) {
self.webView.loadHTMLString(string, baseURL: task.currentRequest?.url)
}
}
}

使用代理的方式,可以处理很多特殊的情况:

  • authentication challenges
  • following redirects

URL Loading System