KingFisher使用URLSession来进行网络请求操作。刨去一切无关元素,让我们来看一下最简单的URLSession:
var session: URLSession? = URLSession(
configuration: URLSessionConfiguration.ephemeral,
delegate: URLSessionDelegate.class,
delegateQueue: nil)
let session1 = session!.dataTask(with: URL(string: "https://api.xxxxx.com/hello")!)
session1.resume()
网络请求就是定义一个使用协议和configuration定义一个URLSession,每个网络请求都是这个session通过dataTask函数生成的task,task有run(运行),cancel(取消),suspend(挂起)等状态。其初始化后,task处于挂起状态,通过resume激活。
在上一篇中,我们知道KingFisher的下载图片使用了ImageDownloader的downloadImage方法:
class ImageDownloader {
func downloadImage(
with url: URL,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask? {
}
其本质上就是对URLSession的一套封装.
1. 主要流程说明
1. 首先 创立一个不使用缓存的URLRequest
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
2. 其次 将 completionHandler block 转换成 TaskCallback
let onCompleted = completionHandler.map {
block -> Delegate<Result<ImageLoadingResult, KingfisherError>, Void> in
let delegate = Delegate<Result<ImageLoadingResult, KingfisherError>, Void>()
delegate.delegate(on: self) { (_, callback) in
block(callback)
}
return delegate
}
let callback = SessionDataTask.TaskCallback(
onCompleted: onCompleted,
options: options
)
3. 生成DownloadTask
DownloadTask是一个结构体, 包括
public struct DownloadTask {
public let sessionTask: SessionDataTask
public let cancelToken: SessionDataTask.CancelToken
}
ImageDownloader通过一个sessionDelegate维持一个 SessionDataTask的队列。
通过url查询是否存在相同url的SessionDataTask,如存在,则根据这个SessionDataTask生成DownloadTask。
否则使用session.dataTask生成一个URLSessionDataTask,将其转换成SessionDataTask,并将其加入到task队列中
4. 配置回调
SessionDataTask的回调是通过Delegate onTaskDone中进行处理的。
onTaskDone的定义如下:
let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>()
它的配置如下:
sessionTask.onTaskDone.delegate(on: self) { (self, done) in
let (result, callbacks) = done
let value = try result.get()
switch result {
case .success(let (data, response)):
let imageResult = result.map { ImageLoadingResult(image: $0, url: url, originalData: data) }
let queue = callback.options.callbackQueue
queue.execute { callback.onCompleted?.call(imageResult) }
}
5. 启动
sessionTask.resume()
接下来,我们会对请求中涉及的数据结构来详细解释
2. Delegate 与 Result
在代码中,我们涉及了两类特殊的数据:Delegate
与 Result
block -> Delegate<Result<ImageLoadingResult, KingfisherError>, Void>
这两个数据在KingFisher中非常通用,需要进行解释一下
2.1 Delegate
Delegate
可以理解成一个block
的包裹器,但是可以避免使用 self,从而防止循环引用。
class Delegate<Input, Output> {
init() {}
private var block: ((Input) -> Output?)?
func delegate<T: AnyObject>(on target: T, block: ((T, Input) -> Output)?) {
self.block = { [weak target] input in
guard let target = target else { return nil }
return block?(target, input)
}
}
func call(_ input: Input) -> Output? {
return block?(input)
}
}
他的使用方法很简单:
- 通过
func delegate
设置回调
let delegate = Delegate<Result<ImageLoadingResult, KingfisherError>, Void>()
delegate.delegate(on: self) { (_, callback) in
block(callback)
}
delegate
接受Input:Result<ImageLoadingResult, KingfisherError>
, 输出Void
- 适应
func call
进行调用
delegate.call(imageResult)
->
2.2 Result
Result
是Swift5
引入的一种枚举,接受Success
与Failure
两种数据,其中Failure
必须继承Error
。用户在使用的时候可以通过
success
或者failure
两种结构获取数据。特别适合在网络请求等可能出现错误的场合使用,用户只需要提供一种回调即可。
public enum Result<Success, Failure> where Failure : Error {
/// A success, storing a `Success` value.
case success(Success)
/// A failure, storing a `Failure` value.
case failure(Failure)
}
其定义如下:
let result: Result<URLResponse, KingfisherError>
if let error = error {
result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error)))
} else if let response = task.response {
result = .success(response)
} else {
result = .failure(KingfisherError.responseError(reason: .noURLResponse(task: sessionTask)))
}
其使用有两种办法:
- 使用
get
,如果失败会直接throw error
do {
let value = try result.get()
} catch {
}
- 使用
Switch
switch result {
//URLResponse
case .success(let response):
case .failure(let error):
}
3. 网络请求task的封装管理
在第一小节的生成DownloadTask
中,我们提到了好几种Task
DownloadTask
、SessionDataTask
、URLSessionDataTask
其中URLSessionDataTask
是网络请求的抽象task,这里暂时不讨论。
此外,我们提到这些task被存在一个队列中。这个管理队列是由
private let sessionDelegate: SessionDelegate
进行管理的
3.1 SessionDataTask
SessionDataTask
是URLSessionDataTask
的封装,可以为URLSessionDataTas
k添加一系列回调,并对这些回调进行管理。至于为何会一个SessionDataTask
对应多个回调,我们会在下面介绍DownloadTask
的时候进行学习
public class SessionDataTask {
//*******网络请求Task的管理************
public let task: URLSessionDataTask // 封装的网络请求task
public private(set) var mutableData: Data // 封装的task请求中下载的原始数据
var started = false // task 是否进入请求状态
//*******网络请求Task回调CallBack的管理************
struct TaskCallback {
let onCompleted: Delegate<Result<ImageLoadingResult, KingfisherError>, Void>? // CallBack的本体
let options: KingfisherParsedOptionsInfo // 一些配置数据
}// 回调CallBack的封装
public typealias CancelToken = Int // CancelToken是取消回调的凭证
private var callbacksStore = [CancelToken: TaskCallback]() // CallBack将存储于此,方便取消
var callbacks: [SessionDataTask.TaskCallback] // 将callbacksStore数据转成Array
func addCallback(_ callback: TaskCallback) -> CancelToken //添加回调
func removeCallback(_ token: CancelToken) -> TaskCallback? //移除回调
func cancel(token: CancelToken) {
let callback = removeCallback(token)
onCallbackCancelled.call((token, callback))
} //移除回调并启用cancel回调,如果callbacksStore没有数据了,直接cancelTask
//*******SessionDataTask自身的两个Delegate,回调TaskCallback通过这两个Delegate通知给外界************
let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>()
//Input: (Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]) 这样一个元组
let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>()
//Input: (CancelToken, TaskCallback) 这样一个元组,onCallbackCancelled 回调是给SessionDelegate使用的
}
onTaskDone在第一节第四小节的配置回调中配置。
onCallbackCancelled在下面要将的SessionDelegate中配置。
3.2 DownloadTask
DownloadTask是一个结构体,是SessionDataTask的封装,也是downImage函数的返回值。
多个DownloadTask
可以关联到同一个SessionDataTask
上。还记得SessionDataTask
可以有多个callback吗,就是为了这个场景准备的.
其结构如下:
public struct DownloadTask {
public let sessionTask: SessionDataTask //关联到此`DownloadTask`的`SessionDataTask`
public let cancelToken: SessionDataTask.CancelToken //DownloadTask的token,用于取消自己
public func cancel() {
sessionTask.cancel(token: cancelToken)
}
}
3.3 SessionDelegate
SessionDelegate
提供了对task的管理队列,这个task是3.1中的SessionTask
. 此外,SessionDelegate
实现了URLSessionDataDelegate
, 提供了一系列Delegate
对网络请求的状态进行管理.
private var tasks: [URL: SessionDataTask] = [:] //URL:SessionDataTask的字典
func add(
_ dataTask: URLSessionDataTask,
url: URL,
callback: SessionDataTask.TaskCallback) -> DownloadTask {// 添加url:task 入tasks
let task = SessionDataTask(task: dataTask) // 生成 SessionDataTask
task.onCallbackCancelled.delegate(on: self) // 设置task的取消回调
{ [unowned task] (self, value) in
let (token, callback) = value
let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token))
task.onTaskDone.call((.failure(error), [callback])) // task的取消回调对外界不会暴露,调用onTaskDone
if !task.containsCallbacks {
let dataTask = task.task
self.remove(dataTask) //要是SessionDataTask对应的DownloadTask全部被取消,根据SessionDataTask对应的URLSessionTask的url,将SessionDataTask从tasks中取消掉
}
}
let token = task.addCallback(callback) //为SessionDataTask绑定callback
tasks[url] = task
return DownloadTask(sessionTask: task, cancelToken: token) //为SessionDataTask绑定DownloadTask
}
func task(for task: URLSessionTask) -> SessionDataTask? //根据URLSessionTask查出SessionDataTask
func task(for url: URL) -> SessionDataTask? //根据 URL查出SessionDataTask
//##### 一系列Delegate, 供给URLSessionDataDelegate
let onValidStatusCode = Delegate<Int, Bool>()
let onDownloadingFinished = Delegate<(URL, Result<URLResponse, KingfisherError>), Void>()
let onDidDownloadData = Delegate<SessionDataTask, Data?>()
let onReceiveSessionChallenge = Delegate<SessionChallengeFunc, Void>()
let onReceiveSessionTaskChallenge = Delegate<SessionTaskChallengeFunc, Void>()
SessionDelegate实现了URLSessionDataDelegate,对URLSessionTask的返回进行包装和处理。以
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
举例,这个函数是URLSessionTask请求完成的回调。
首先
,我们从tasks中找到对应的SessionTask
guard let sessionTask = self.task(for: task) else { return }
其次
,这个时候下载已经完成,我们需要处理 Delegate onDownloadingFinished
let onDownloadingFinished = Delegate<(URL, Result<URLResponse, KingfisherError>), Void>()
新建一个Result<URLResponse, KingfisherError>,根据error是否为空,为其赋予.success()与.failure()
其次
,这个时候下载的data已经获取到,我们需要处理 Delegate onDidDownloadData,我们可以这个Delegate中对Data进行统一处理。
let onDidDownloadData = Delegate<SessionDataTask, Data?>()
let data = onDidDownloadData.call(sessionTask)
最后
,如果Data存在且error不为空,移除URLSessionTask,调用SesssionTask的onTaskDone回调,通知外界数据已经成功获取。
sessionTask.onTaskDone.call((result, sessionTask.callbacks))
网友评论