前言
这篇文章,咱们来说说Task代理,通过之前的文章,我们可以知道一个普通的网络请求过程是:
- 根据一个
URL和若干的参数来生成Request。- 根据
Request生成一个会话Session。- 再根据这个
Session生成Task。- 我们开启
Task就完成了这个请求。
在这个请求过程中,还有重定向、数据的上传、证书的验证、配置等信息。
其实,我们做iOS开发的,对代理这个问题,不管是在网络请求中,还是用于代理回调等地方,我觉得代理就好比是一个拥有较高权限的管理员。这种方式在我们做业务开发中,是很好的处理方式。
URLSessionTask
Task分类
在苹果原生网络框架中,URLSessionTask是最基础的task任务封装,主要有以下几种task:
URLSessionDataTaskURLSessionUploadTaskURLSessionDownloadTaskURLSessionStreamTask
注意:URLSessionStreamTask这个我们先暂时不介绍,在后面的文章中单独介绍说明。
继承关系
我们先把URLSessionTask的相关继承关系图给到大家看一下,具体代理方法我们后面慢慢讲。
URLSessionTask
URLSessionTask子类继承关系:
URLSessionTask子类.png
URLSessionTaskDelegate
URLSessionTaskDelegate继承自URLSessionDelegate,URLSessionTaskDelegate的主要协议方法有:
optional func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
optional func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
optional func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
// 上传比较特殊一点,只有这一个跟上传相关的代理方法
URLSessionDataDelegate
URLSessionDataDelegate则是继承自URLSessionTaskDelegate协议,它的主要协议方法有:
optional func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
optional func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask)
optional func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
optional func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void)
URLSessionDownloadDelegate
URLSessionDownloadDelegate同样是继承自URLSessionTaskDelegate协议,主要方法有:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
optional func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
optional func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64)
TaskDelegate
在Alamofire中,咱们可以看到,TaskDelegate类继承自NSObject,处于继承链的最底层,它为我们提供了一些基础的属性,而且,这些属性是和其他的Delegate共享使用:
// MARK: Properties
/// The serial operation queue used to execute all operations after the task completes.
public let queue: OperationQueue
/// The data returned by the server.
public var data: Data? { return nil }
/// The error generated throughout the lifecyle of the task.
public var error: Error?
var task: URLSessionTask? {
set {
taskLock.lock(); defer { taskLock.unlock() }
_task = newValue
}
get {
taskLock.lock(); defer { taskLock.unlock() }
return _task
}
}
var initialResponseTime: CFAbsoluteTime?
var credential: URLCredential?
var metrics: AnyObject? // URLSessionTaskMetrics
private var _task: URLSessionTask? {
didSet { reset() }
}
private let taskLock = NSLock()
我们接下来分析一下这些属性。
属性简介
queue
queue: OperationQueue,显而易见,这就是一个队列,在队列中,我们可以添加很多的operation,而且,当我们把isSuspended的值设置为true,就可以让队列中的所有operation暂停,如果想要继续执行operation,我们需要把isSuspended的值设置为false。
在Alamofire框架中,有以下几种情况,会加入该队列的operation:
- 队列在任务完成后,把
isSuspended的值设置为false。- 在任务完成后调用
Request的endTime,就可以为Request设置请求结束时间。- 在处理
response中,Alamofire中的响应回调是链式的,原理就是把这些回调函数通过operation添加到队列中,因此也保证了回调函数的访问顺序是正确的。- 还有上传数据成功后,删除一些临时文件等操作。
data
data: Data?,服务器返回的Data,?表示这个可能为空。
error
error: Error?,这个应该很好理解了,在网络请求过程中,很有可能出现错误,那么我们添加这个属性,就是为了抓取过程中出现的错误。
task
task: URLSessionTask?,就是表示一个task,很重要的属性。
initialResponseTime
initialResponseTime: CFAbsoluteTime?,这是一个task的响应时间,如果是URLSessionDataTask,则表示接收到数据的时间;如果是URLSessionDownloadTask,则表示开始写数据的时间;如果是URLSessionUploadTask,则表示上传数据的时间。
credential
credential: URLCredential?,这个表示证书,在做证书验证的时候用到。
metrics
metrics: AnyObject?,这是苹果提供的一个统计task信息的类URLSessionTaskMetrics,它可以统计相关任务的事务,任务开始时间、结束时间,以及重定向的次数等信息。
生命周期Lifecycle
首先,还是来看一看代码:
// MARK: Lifecycle
init(task: URLSessionTask?) {
_task = task
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
}
func reset() {
error = nil
initialResponseTime = nil
}
init函数
在初始化函数中,主要就是设置
operationQueue,operationQueue.isSuspended = true,就可以保证队列中的operation都是暂停的,通常情况下,operation在被加入到队列中后,会立即执行。
reset函数
reset函数把error和initialResponseTime都置为nil,这个就很简单了,不说了。
URLSessionTaskDelegate
接下来,我们看看URLSessionTaskDelegate的相关函数:
// MARK: URLSessionTaskDelegate
var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
taskWillPerformHTTPRedirection
首先来看一下第一个代理方法:
@objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
var redirectRequest: URLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
根据函数名的意思,我们应该知道,这个函数就是用来处理重定向问题的,这个函数要求返回一个redirectRequest,顾名思义,就是重定向Request的,处理方式就是:如果给代理的重定向函数赋值了,就会返回代理函数的返回值,否则返回服务器的Request。
taskDidReceiveChallenge
这个方法就是用来处理请求验证相关的,看一下:
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if
let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
let serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
disposition = .useCredential
credential = URLCredential(trust: serverTrust)
} else {
disposition = .cancelAuthenticationChallenge
}
}
} else {
if challenge.previousFailureCount > 0 {
disposition = .rejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
首先,我们来看一下
disposition,它的类型是URLSession.AuthChallengeDisposition,这个类型就是一个枚举类型:
@available(iOS 7.0, *)
public enum AuthChallengeDisposition : Int {
case useCredential /* Use the specified credential, which may be nil */
case performDefaultHandling /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */
case cancelAuthenticationChallenge /* The entire request will be canceled; the credential parameter is ignored. */
case rejectProtectionSpace /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */
}
useCredential:使用的证书performDefaultHandling:采用默认的方式,和服务器返回的authenticationMethod有很大关系cancelAuthenticationChallenge:取消认证rejectProtectionSpace:拒绝认证
我们在进行验证的时候,有三种验证方式:
- 客户端验证
- 服务器验证
- 双向验证
从上面的函数方法,可以知道,如果服务器需要验证客户端,只需要给TaskDelegate的taskDidReceiveChallenge赋值就可以了。
在Alamofire的双向验证中,客户端和服务端如果需要建立SSL,只需要2步可以完成:
- 服务端返回
WWW-Authenticate响应头,并返回自己信任证书- 客户端验证证书,然后用证书中的公钥把数据加密后发送给服务端
taskNeedNewBodyStream
同样的,我们先来看一下代码:
@objc(URLSession:task:needNewBodyStream:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
var bodyStream: InputStream?
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
bodyStream = taskNeedNewBodyStream(session, task)
}
completionHandler(bodyStream)
}
当给
task的Request提供一个body stream时才会调用,我们不需要关心这个方法,即使我们通过fileURL或者NSData上传数据时,该函数也不会被调用。
taskDidCompleteWithError
一样的,先看代码:
@objc(URLSession:task:didCompleteWithError:)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
if
let downloadDelegate = self as? DownloadTaskDelegate,
let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
{
downloadDelegate.resumeData = resumeData
}
}
queue.isSuspended = false
}
}
该函数在请求完成后被调用,值得注意的是
error不为nil的情况,除了给自身的error属性赋值外,针对下载任务做了特殊处理,就是把当前已经下载的数据保存在downloadDelegate.resumeData中,有点像断点下载。
DataTaskDelegate
属性
首先,还是先来看一下它的属性:
// MARK: Properties
var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
override var data: Data? {
if dataStream != nil {
return nil
} else {
return mutableData
}
}
var progress: Progress
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var dataStream: ((_ data: Data) -> Void)?
private var totalBytesReceived: Int64 = 0
private var mutableData: Data
private var expectedContentLength: Int64?
dataTask: URLSessionDataTask:DataTaskDelegate管理URLSessionDataTaskdata: Data?:同样是返回Data,但这里有一点不同,如果定义了dataStream方法的话,这个data返回为nilprogress: Progress:进度,这个就不解释了progressHandler:这不是函数,是一个元组,我们等下具体说说dataStream:自定义的数据处理函数totalBytesReceived:已经接收的数据mutableData:保存数据的容器expectedContentLength:接收的数据的总大小
生命周期
先看代码:
// MARK: Lifecycle
override init(task: URLSessionTask?) {
mutableData = Data()
progress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
progress = Progress(totalUnitCount: 0)
totalBytesReceived = 0
mutableData = Data()
expectedContentLength = nil
}
这些很简单的,没有什么可说的。
方法调用
主要是看看它的函数方法:
// MARK: URLSessionDataDelegate
var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
dataTaskDidReceiveResponse
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
{
var disposition: URLSession.ResponseDisposition = .allow
expectedContentLength = response.expectedContentLength
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
当收到服务端的响应后,该方法被触发。在这个函数中,我们能够获取到和数据相关的一些参数。
dataTaskDidBecomeDownloadTask
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didBecome downloadTask: URLSessionDownloadTask)
{
dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
}
在
dataTaskDidReceiveResponse方法中,disposition的类型是URLSession.ResponseDisposition,这是一个枚举类型:
@available(iOS 7.0, *)
public enum ResponseDisposition : Int {
case cancel /* Cancel the load, this is the same as -[task cancel] */
case allow /* Allow the load to continue */
case becomeDownload /* Turn this request into a download */
@available(iOS 9.0, *)
case becomeStream /* Turn this task into a stream task */
}
因此,当我们设置成
becomeDownload时,dataTaskDidBecomeDownloadTask方法就会被调用,创建了一个新的downloadTask。
dataTaskDidReceiveData
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data)
} else {
mutableData.append(data)
}
let bytesReceived = Int64(data.count)
totalBytesReceived += bytesReceived
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
这个方法会把数据放入对象中,对自定义函数和进度信息进行处理。
dataTaskWillCacheResponse
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void)
{
var cachedResponse: CachedURLResponse? = proposedResponse
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
}
completionHandler(cachedResponse)
}
该函数用于处理是否需要缓存响应,
Alamofire默认是缓存这些response的,但是每次发请求,它不会再缓存中读取。
DownloadTaskDelegate
属性
// MARK: Properties
var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask }
var progress: Progress
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var resumeData: Data?
override var data: Data? { return resumeData }
var destination: DownloadRequest.DownloadFileDestination?
var temporaryURL: URL?
var destinationURL: URL?
var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
有和上面重复的属性,我就不说了,看下没有重复的部分:
downloadTask:和URLSessionDownloadDelegate相对应的URLSessionDownloadTask
resumeData:在上边我们提到过,当请求完成后,如果error不为nil,如果是DownloadTaskDelegate,就会给这个属性赋值data:返回resumeDatadestination:通过这个函数可以自定义文件保存目录和保存方式,这个保存方式分两种,为URl创建文件夹,删除已经下载且存在的文件,这个会在后续的文章中提到
temporaryURL:临时的URL
destinationURL:数据存储URL
fileURL:fileURL返回文件的路径,如果destination不为nil,就返回destinationURL,否则返回temporaryURL
生命周期
// MARK: Lifecycle
override init(task: URLSessionTask?) {
progress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
progress = Progress(totalUnitCount: 0)
resumeData = nil
}
方法调用
代理方法有三个:
// MARK: URLSessionDownloadDelegate
var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)?
var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
downloadTaskDidFinishDownloadingToURL
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
temporaryURL = location
guard
let destination = destination,
let response = downloadTask.response as? HTTPURLResponse
else { return }
let result = destination(location, response)
let destinationURL = result.destinationURL
let options = result.options
self.destinationURL = destinationURL
do {
if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(at: destinationURL)
}
if options.contains(.createIntermediateDirectories) {
let directory = destinationURL.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
}
try FileManager.default.moveItem(at: location, to: destinationURL)
} catch {
self.error = error
}
}
当数据下载完成后,该函数被触发。系统会把数据下载到一个临时的
locationURL的地方,我们就是通过这个URL拿到数据的。上边函数内的代码主要是把数据复制到目标路径中。
downloadTaskDidWriteData
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(
session,
downloadTask,
bytesWritten,
totalBytesWritten,
totalBytesExpectedToWrite
)
} else {
progress.totalUnitCount = totalBytesExpectedToWrite
progress.completedUnitCount = totalBytesWritten
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
该代理方法在数据下载过程中被触发,主要的作用就是提供下载进度。
downloadTaskDidResumeAtOffset
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
} else {
progress.totalUnitCount = expectedTotalBytes
progress.completedUnitCount = fileOffset
}
}
如果一个下载的
task是可以恢复的,那么当下载被取消或者失败后,系统会返回一个resumeData对象,这个对象包含了一些跟这个下载task相关的一些信息,有了它就能重新创建下载task,创建方法有两个:downloadTask(withResumeData:)和downloadTask(withResumeData:completionHandler:),当task开始后,上边的代理方法就会被触发。
UploadTaskDelegate
属性
// MARK: Properties
var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask }
var uploadProgress: Progress
var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
这些属性又得已经重复了,就不多说了。
生命周期
// MARK: Lifecycle
override init(task: URLSessionTask?) {
uploadProgress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
uploadProgress = Progress(totalUnitCount: 0)
}
这也不用多说啥,主要的还是看看方法调用。
方法调用
这里只有一个方法调用:
// MARK: URLSessionTaskDelegate
var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
func URLSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else {
uploadProgress.totalUnitCount = totalBytesExpectedToSend
uploadProgress.completedUnitCount = totalBytesSent
if let uploadProgressHandler = uploadProgressHandler {
uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) }
}
}
}
该函数主要目的是提供上传的进度,在
Alamofire中,上传数据用的是stream,这个会在后续文章中给出详细的解释。
总结
学习框架的时候,好多东西当时记住了,可能一会儿就忘了,写下这篇文章的目的,就是为了加深理解印象,方便以后查阅笔记,如果文章有错误,还望指出,还得感谢一下这位朋友马在路上。










网友评论