阅读 480

Kingfisher 源码阅读笔记(5)——网络请求

这是我参与更文挑战的第15天,活动详情查看: 更文挑战

本文为在阅读 Kingfisher 源码时的收货。

其他系列文章

Kingfisher 源码阅读笔记(1)

Kingfisher 源码阅读笔记(2)

Kingfisher 源码阅读笔记(3)

Kingfisher 源码阅读笔记(4)

Kingfisher 中负责图片下载逻辑的类主要有:

  • ImageDownloader 负责添加、移除网络请求的主要类;
  • SessionDelegate 负责所有的 url 和对应 SessionDataTask,并作为 URLSessionDataTask 的代理,处理请求过程。
  • SessionDataTask 负责一个 URLSessionDataTask 和与其关联的所有回调通知;

从 URL 到 UIImage 的过程

从一个 URL 到 UIImage 的整个过程包括:

  1. 根据 URL 创建 URLRequest,设置是否启用管道(pipelining),低数据模式(Low Data Mode)时连接是否可以使用网络,
  2. 查看用户是否需要修改 URLRequest,例如:在 header 中添加 token,执行基本的 HTTP 身份验证,或者 url 映射之类的操作。
  3. 查看是否已经存在 URLRequest 对应的 URLSessionDataTask,避免相同的 URLRequest 创建多个 URLSessionDataTask。如果不存在,则创建 URLSessionDataTask,并设置其优先级;
  4. 设置 URLSessionDataTask 的回调,然后开始任务;
  5. 处理网络请求中可能出现的认证质询、重定向的问题。
  6. 从服务器接受到数据时通知外界;
  7. 如果用户取消一个网络请求,则只会移除对应的回调,并不会真正的将 URLSessionDataTask 停止。除非所有监听的 URLSessionDataTask 的回调全部取消。
  8. 过滤 http 的 statusCode,默认可以接受的值为 200 到 400;
  9. 在接收到所有的数据 data 之后,将 data 转换为 UIImage;
  10. 判断是否需要对 UIImage 进行解码,如果需要解密,则会在 CGContext 中绘制图像并从中返回数据。当 UIImage 刚刚从数据创建但尚未首次显示时,解码可以提高绘图性能,但是需要更多的时间来准备数据;
  11. 在用户指定的队列通知最终的数据结果。

URLSession

Kingfisher 内部使用 URLSession 处理处理网络请求。初始化 URLSession 的配置的配置为 URLSessionConfiguration.ephemeral

open var sessionConfiguration = URLSessionConfiguration.ephemeral {
        didSet {
            session.invalidateAndCancel()
            session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
        }
    }
复制代码

URLSessionConfiguration 用来配置 session 的属性,比如:timeout、HTTP header、缓存策略。有三种创建方式:

  • URLSessionConfiguration.default:默认实例创建方式,在硬盘上持久化全局缓存,存储证书(credential)到用户钥匙链,存储 cookie 到 shareCookie;
  • URLSessionConfiguration.ephemeral:唯一跟 default 不一样的是所以与会话(session)相关的数据都存储在内存中;
  • URLSessionConfiguration.background(withIdentifier:):让会话在后台执行上载或下载任务。即使应用程序本身被暂停或终止,传输仍将继续。

设置 URLRequest 的管道、低数据模式

Kingfisher 创建网络请求 URLRequest 的代码如下:

var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
request.httpShouldUsePipelining = requestsUsePipelining
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *),
   options.lowDataModeSource != nil {
    request.allowsConstrainedNetworkAccess = false
}
复制代码

其中:

  • httpShouldUsePipelining:设置是否启用管道(pipelining)。默认情况下请求和响应是顺序进行的, 也就是说一个请求发出以后,在得到响应之后,才能发送第二个请求。如果将 httpShouldUsePipelining 设置为 true,则允许在接收到前一个响应之前发送新的请求。
  • allowsConstrainedNetworkAccess:设置当用户指定低数据模式(Low Data Mode)时连接是否可以使用网络,默认为 true。

判断 URLSessionDataTask 是否已取消

Kingfisher 的作者在判断 URLSessionDataTask 是否已经被取消时,不是通过 task.state != .running 进行判断的。因为,有时取消任务时,并不会立即变成 .cancelling。所以,作者通过回调监听的数量进行判断的。

We should be able to use task.state != .running to check it. However, in some rare cases, cancelling the task does not change task state to .cancelling immediately, but still in .running. So we need to check callbacks count to for sure that it is safe to remove the task in delegate.

private var callbacksStore = [CancelToken: TaskCallback]()

var containsCallbacks: Bool {
    return !callbacks.isEmpty
}

var callbacks: [SessionDataTask.TaskCallback] {
    lock.lock()
    defer { lock.unlock() }
    return Array(callbacksStore.values)
}
复制代码

其中 CancelToken 为回调对应的唯一数值,TaskCallback 为对应的回调。

文章分类
iOS
文章标签