美文网首页
【Alamofire源码解析】08 - Request

【Alamofire源码解析】08 - Request

作者: Lebron_James | 来源:发表于2017-12-21 22:46 被阅读79次

这个文件里面主要定义了各种请求类型。

1. RequestAdapter协议

RequestAdapter协议允许SessionManagerRequest在创建的时候被适配。

public protocol RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}

举个例子,大家就知道RequestAdapter用来干嘛的了。例如我们在请求的时候需要把accessToken拼接到请求头:

class AccessTokenAdapter: RequestAdapter {
    private let accessToken: String

    init(accessToken: String) {
        self.accessToken = accessToken
    }

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest

        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://httpbin.org") {
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
        }

        return urlRequest
    }
}

// 使用
let sessionManager = SessionManager()
sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")

// 在创建请求的时候就会把accessToken加进去
sessionManager.request("https://httpbin.org/get")

2. 重试

// 一个closure类型,给`RequestRetrier`判断是否需要重试
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void

// 请求重试器协议
public protocol RequestRetrier {
    // 决定请求是否需要重试
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}

3. TaskConvertible协议

这个协议的主要目的是,让遵循这个协议的类型,通过实现协议的方法来创建URLSessionTask

protocol TaskConvertible {
    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
}

4. Request

1)辅助类型

// 监测上传或者下载进度的closure类型
public typealias ProgressHandler = (Progress) -> Void

// 列举了请求任务的类型,并携带了相关的关联值
enum RequestTask {
    case data(TaskConvertible?, URLSessionTask?)
    case download(TaskConvertible?, URLSessionTask?)
    case upload(TaskConvertible?, URLSessionTask?)
    case stream(TaskConvertible?, URLSessionTask?)
}

2)属性

// TaskDelegate,用于处理URLSessionTask的所有callback
// internal(set): 这个指的是只能在内部赋值
// get和set都加了锁,防止多线程同时get和set
open internal(set) var delegate: TaskDelegate {
    get {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        return taskDelegate
    }
    set {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        taskDelegate = newValue
    }
}

open var task: URLSessionTask? { return delegate.task }
open let session: URLSession
open var request: URLRequest? { return task?.originalRequest }
open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
open internal(set) var retryCount: UInt = 0
let originalTask: TaskConvertible?

// 用于记录请求的时间
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?

var validations: [() -> Void] = []

private var taskDelegate: TaskDelegate
private var taskDelegateLock = NSLock()

3)初始化方法

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
    self.session = session

    // 根据请求任务的类型,创建taskDelegate
    switch requestTask {
    case .data(let originalTask, let task):
        taskDelegate = DataTaskDelegate(task: task)
        self.originalTask = originalTask
    case .download(let originalTask, let task):
        taskDelegate = DownloadTaskDelegate(task: task)
        self.originalTask = originalTask
    case .upload(let originalTask, let task):
        taskDelegate = UploadTaskDelegate(task: task)
        self.originalTask = originalTask
    case .stream(let originalTask, let task):
        taskDelegate = TaskDelegate(task: task)
        self.originalTask = originalTask
    }

    delegate.error = error
    
    把请求结束的时间用队列记录下来,在请求完成后会执行这个队列
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}

4)验证

下面的前两个方法把Self作为返回值,方便我们把同一个类的两个方法链接起来调用,例如:

let user = "user"
let password = "password"

Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
    .authenticate(user: user, password: password)
    .responseJSON { response in
        debugPrint(response)
}

// 可以通过这个方法来关联一个HTTP基础证书
@discardableResult
open func authenticate(
    user: String,
    password: String,
    persistence: URLCredential.Persistence = .forSession)
    -> Self
{
    let credential = URLCredential(user: user, password: password, persistence: persistence)
    return authenticate(usingCredential: credential)
}

// 可以通过这个方法来关联一个指定的证书
@discardableResult
open func authenticate(usingCredential credential: URLCredential) -> Self {
    delegate.credential = credential
    return self
}

// 返回一个base64编码的验证头
open static func authorizationHeader(user: String, password: String) -> (key: String, value: String)? {
    guard let data = "\(user):\(password)".data(using: .utf8) else { return nil }

    let credential = data.base64EncodedString(options: [])

    return (key: "Authorization", value: "Basic \(credential)")
}

5)状态

/// 开始请求
open func resume() {
    guard let task = task else { delegate.queue.isSuspended = false ; return }

    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }

    task.resume()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidResume,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

/// 暂停请求
open func suspend() {
    guard let task = task else { return }

    task.suspend()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidSuspend,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

/// 取消请求
open func cancel() {
    guard let task = task else { return }

    task.cancel()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidCancel,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

另外还实现了CustomStringConvertibleCustomDebugStringConvertible,方便调试使用。

5. DataRequest

继承于Request

1) Requestable

内置了Requestable,并实现TaskConvertible协议。目的是用urlRequest创建一个dataTask。

struct Requestable: TaskConvertible {
    let urlRequest: URLRequest

    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
}

2) 属性

// DataRequest对应的请求
open override var request: URLRequest? {
    if let request = super.request { return request }
    if let requestable = originalTask as? Requestable { return requestable.urlRequest }

    return nil
}

// 从服务器获取数据的进度
open var progress: Progress { return dataDelegate.progress }

// DataTask对应的delegate,负责处理与data task相关的所有回调。在父类的初始化方法已经初始化过,这里直接返回,并且强转为DataTaskDelegate类型
// 在使用强转的时候要非常小心,如果不能确定能强转成功的,请务必使用`if let`,否则如果强转不成功,程序crash
var dataDelegate: DataTaskDelegate { return delegate as! DataTaskDelegate }

// 调用这个方法,设置一个closure,这个closure会在数据从服务器返回过程中周期性的调用;
// closure里面的data只包含最近从服务器获取的数据,不包含之前获取的数据;
// 如果调用了这个方法设置closure,那么下载的数据只能从这里访问,不会在其他地方存储,
// 并且`Response`对象中的data为`nil`
@discardableResult
open func stream(closure: ((Data) -> Void)? = nil) -> Self {
    dataDelegate.dataStream = closure
    return self
}

// 调用这个方法,设置一个closure,这个closure会在数据从服务器返回过程中周期性的调用,用于跟踪下载进度
@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
    dataDelegate.progressHandler = (closure, queue)
    return self
}

6. DownloadRequest

继承于Request

1) DownloadOptions

是一个OptionSet类型,用于指定一些下载选项:

public struct DownloadOptions: OptionSet {
    public let rawValue: UInt

    // 创建中间目录
    public static let createIntermediateDirectories = DownloadOptions(rawValue: 1 << 0)

    // 删除之前的文件
    public static let removePreviousFile = DownloadOptions(rawValue: 1 << 1)

    public init(rawValue: UInt) {
        self.rawValue = rawValue
    }
}

2) DownloadFileDestination

下载完成后,会执行这个closure,程序会把下载完成后把文件临时存放在temporaryURL,然后再移动到destinationURL

public typealias DownloadFileDestination = (
    _ temporaryURL: URL,
    _ response: HTTPURLResponse)
    -> (destinationURL: URL, options: DownloadOptions)

3) Downloadable

内置了Downloadable,并实现TaskConvertible协议。目的是用urlRequest创建一个downloadTask。创建下载任务的时候有两种情况:1)全新的下载;2)之前下载好了部分数据,接着继续下载。

enum Downloadable: TaskConvertible {
    case request(URLRequest)
    case resumeData(Data)

    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let task: URLSessionTask

            switch self {
            case let .request(urlRequest):
                let urlRequest = try urlRequest.adapt(using: adapter)
                task = queue.sync { session.downloadTask(with: urlRequest) }
            case let .resumeData(resumeData):
                task = queue.sync { session.downloadTask(withResumeData: resumeData) }
            }

            return task
        } catch {
            throw AdaptError(error: error)
        }
    }
}

4) 属性

// DownloadRequest对应的请求
open override var request: URLRequest? {
    if let request = super.request { return request }

    if let downloadable = originalTask as? Downloadable, case let .request(urlRequest) = downloadable {
        return urlRequest
    }

    return nil
}

// 已经下载好的数据,用于继续下载
open var resumeData: Data? { return downloadDelegate.resumeData }

// 下载进度
open var progress: Progress { return downloadDelegate.progress }

// // DownloadTask对应的delegate,负责处理URLSessionDownloadDelegate的所有回调。在父类的初始化方法已经初始化过,这里直接返回,并且强转为DownloadTaskDelegate类型
var downloadDelegate: DownloadTaskDelegate { return delegate as! DownloadTaskDelegate }

5) 方法


/// 重写父类的cancel方法
open override func cancel() {
  // 取消下载的时候要把resumeData记录起来,方便后续继续下载
    downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidCancel,
        object: self,
        userInfo: [Notification.Key.Task: task as Any]
    )
}

// 调用这个方法,设置一个closure,这个closure会在数据从服务器返回过程中周期性的调用,用于跟踪下载进度
@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
    downloadDelegate.progressHandler = (closure, queue)
    return self
}

// 提供一个建议的DownloadFileDestination,最终会把下载好的文件移动到用户的documents目录
open class func suggestedDownloadDestination(
    for directory: FileManager.SearchPathDirectory = .documentDirectory,
    in domain: FileManager.SearchPathDomainMask = .userDomainMask)
    -> DownloadFileDestination
{
    return { temporaryURL, response in
        let directoryURLs = FileManager.default.urls(for: directory, in: domain)

        if !directoryURLs.isEmpty {
            return (directoryURLs[0].appendingPathComponent(response.suggestedFilename!), [])
        }

        return (temporaryURL, [])
    }
}

7. UploadRequest

继承于DataRequest

1) Uploadable

内置了Uploadable,并实现TaskConvertible协议。目的是用urlRequest创建一个uploadTask。创建上传任务的时候有三种情况:1)上传数据;2)上传文件;3)上传流。

enum Uploadable: TaskConvertible {
    case data(Data, URLRequest)
    case file(URL, URLRequest)
    case stream(InputStream, URLRequest)
    
    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let task: URLSessionTask
            
            switch self {
            case let .data(data, urlRequest):
                let urlRequest = try urlRequest.adapt(using: adapter)
                task = queue.sync { session.uploadTask(with: urlRequest, from: data) }
            case let .file(url, urlRequest):
                let urlRequest = try urlRequest.adapt(using: adapter)
                task = queue.sync { session.uploadTask(with: urlRequest, fromFile: url) }
            case let .stream(_, urlRequest):
                let urlRequest = try urlRequest.adapt(using: adapter)
                task = queue.sync { session.uploadTask(withStreamedRequest: urlRequest) }
            }
            
            return task
        } catch {
            throw AdaptError(error: error)
        }
    }
}

2) 属性

// UploadRequest对应的请求
open override var request: URLRequest? {
    if let request = super.request { return request }
    
    guard let uploadable = originalTask as? Uploadable else { return nil }
    
    switch uploadable {
    case .data(_, let urlRequest), .file(_, let urlRequest), .stream(_, let urlRequest):
        return urlRequest
    }
}

// 上传进度
open var uploadProgress: Progress { return uploadDelegate.uploadProgress }

// // UploadTask对应的delegate,负责处理与上传相关的回调。在父类的初始化方法已经初始化过,这里直接返回,并且强转为UploadTaskDelegate类型
var uploadDelegate: UploadTaskDelegate { return delegate as! UploadTaskDelegate }

// 调用这个方法,设置一个closure,这个closure会在数据上传过程中周期性的调用,用于跟踪上传进度
@discardableResult
open func uploadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
    uploadDelegate.uploadProgressHandler = (closure, queue)
    return self
}

8. StreamRequest

继承于Request,只能在iOS、macOS和tvOS使用。同样地,内置了Streamable,并实现TaskConvertible协议。目的是用host和端口或者NetService创建一个streamTask。

@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
open class StreamRequest: Request {
    enum Streamable: TaskConvertible {
        case stream(hostName: String, port: Int)
        case netService(NetService)
        
        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
            let task: URLSessionTask
            
            switch self {
            case let .stream(hostName, port):
                task = queue.sync { session.streamTask(withHostName: hostName, port: port) }
            case let .netService(netService):
                task = queue.sync { session.streamTask(with: netService) }
            }
            
            return task
        }
}

有任何问题,欢迎大家留言!

欢迎加入我管理的Swift开发群:536353151,本群只讨论Swift相关内容。

原创文章,转载请注明出处。谢谢!

相关文章

网友评论

      本文标题:【Alamofire源码解析】08 - Request

      本文链接:https://www.haomeiwen.com/subject/itmzwxtx.html