Alamofire(3)— Request

1,332 阅读5分钟

😊😊😊Alamofire专题目录,欢迎及时反馈交流 😊😊😊


Alamofire 目录直通车 --- 和谐学习,不急不躁!


上一篇 Alamofire-后台下载 其中就介绍了关于 SesssionManagerSessionDelegate的分层!下面我在总结一下,然后开始一个 Alamofire 非常重要的模块 Request!

一、SessionManager的总结

  • SesssionManager 就是对外提供的管理者,这个管理者具备整个 Alamofire 的所有功能。

    • request 请求、download、upload、stream
    • 请求头信息设置以及多表单头信息设置
    • session 直接对外提供,方便自由处理
    • 初始化暴露,方便自由工厂构造,比如 URLSessionConfiguration 的配置模式
    • 重试以及适配请求
    • 闭包对外提供:backgroundCompletionHandler 后台下载回来监听闭包
    • 其中一个非常重要的点就是 SesssionManagerSession 的代理移交给了一个专门的类 : SessionDelegate
  • SessionDelegate实现了 URLSessionDelegateURLSessionTaskDelegateURLSessionDataDelegateURLSessionDownloadDelegateURLSessionStreamDelegate 等代理

  • 更多有意思的是:SessionDelegate还提供了对外的闭包,意味着所有的内部实现的代理情况,再外界都可以进行监听。当然这个所有的对外闭包分为两种情况:

    • 在原来代理回调的内部添加闭包执行。

  • 另一种是二选一,如果代理回来就不执行下层代理下发,执行对外闭包回调

总结:SesssionManager负责创建和管理 Request 对象及其底层NSURLSession

首先给大家贴出一张非常熟悉的图,看懂了这张图对下面理解 Request 的帮助大大的

  • 从上面这张图可以看出,我们的对外模块是SesssionManager,他给外界的用户提供了很多的功能
  • 但是这些工作的真正实现者是由iOS、Android、前端、后台、测试实现的!
  • 其中单拿 iOS 模块的任务来说,有 首页、发现、我的、SDK、视频....模块要实现,但是我们的项目经理有可能都不知道这些到底是什么,怎么实现!所有来说如果全部交给SesssionManager来实现,显然耦合性过强,还有任务乱七八糟,没有体现一个牛逼项目分层架构的效果。所以在 iOS 任务细化和SesssionManager 之间就缺了一个小管理者,对下:他知道具体事务和调度。对上:他能和SesssionManager协调配合。那就是 Request

二、Request

1. Request参数编码

  • 首先说明一下 Alamofire 支持编码格式,具体格式差异根据名字很容易理解,实在不能理解的可以自行百度查漏补缺

    • URLEncoding
    • JSONEncoding
    • PropertyListEncoding
  • let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters) 通过这句代码还编码请求

public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()

    guard let parameters = parameters else { return urlRequest }

    if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
        guard let url = urlRequest.url else {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }

        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
            urlComponents.percentEncodedQuery = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    } else {
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
        }
        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
    }
    return urlRequest
}
  • 代码自行查阅,这里总结一下

  • 首先取出请求方法,根据不同的请求方法,参数编码是不同的

    • .get, .head, .delete 这三个方法是把参数直接拼接到 URL 后面
    • 其他通过请求体 (httpBody) 的形式编码
  • 因为我们的请求是通过 ASCII编码 的,所以要进行百分号编码,第一步就是对当前请求的所有路由百分号编码

  • 参数便利编码, 拼接拿出

private func query(_ parameters: [String: Any]) -> String {
    var components: [(String, String)] = []

    for key in parameters.keys.sorted(by: <) {
        let value = parameters[key]!
        components += queryComponents(fromKey: key, value: value)
    }
    return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
  • 通过 ASCII 有小到大进行排序
  • queryComponents 这个方法代码过度省略。
    • 里面进行递归参数,
    • key和value 取出,然后进行了百分号编码。
    • 放进元组保存,形成参数对
  • 外面讲元组加入数组中
  • map 映射 ($0)=\($1)
  • 元素之间键入一个分隔符号 &

普通方法就直接拼接到URL的后面,例如POST方法就是把这些编码好的参数对放入请求体中。其中还要加入Content-Type的请求头

2. Request内部关系梳理

探索完 request 繁琐事务之一的参数编码之后,开始分析内部:url -> request -> task 的过程。这个过程中还建立 task以及request 之间的绑定关系

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    var originalRequest: URLRequest?
    do {
        originalRequest = try urlRequest.asURLRequest()
        let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
        let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
        let request = DataRequest(session: session, requestTask: .data(originalTask, task))

        delegate[task] = request
        if startRequestsImmediately { request.resume() }

        return request
    } catch {
        return request(originalRequest, failedWith: error)
    }
}
  • 内部创建 Requestable 来帮助下层的 DataRequest 创建 Task,也是常规说法,任务分层,架构思路更清晰
  • 绑定 task 和 request , 方便在 SessionDelegate 下发任务,task 直接检索,request 方便直接获取
  • 直接任务 request.resume() 启动

  • Request 初始化的时候利用了枚举的便利性,构造创建,相关信息保存

可能很多小伙伴这个时候就会有疑虑,你不就是通过SessionDelegate实现代理, 为什么这里还要有一个 DataTaskDelegate

  • 首先一定要明白 SessionDelegate 是所有 Session 的代理遵循者,任何的代理都会来到这里响应。

  • DataTaskDelegate 是我们具体繁琐任务实现者,这里面所有方法都是由 SessionDelegate 下发响应的。

  • 说白了就是整体与局部的关系

  • SessionDelegate 是事件总响应者,但是具体的事务应该交给具体的人去执行,不能因为在 SessionDelegate 能够拿到所有的响应,那么所有的代码都罗列在这里,很显然如果那样做必然会导致混乱,我们根据不同的需求,响应总代理然后根据需求的不同交给专业的人去做专业的事。耦合性大大降低,架构的分层更加明显。

  • 其中还有异步非常重要的点:delegate[task] = request 给我们的SessionDelegate 提供一个方便及时获得能力

open func urlSession(
    _ session: URLSession,
    downloadTask: URLSessionDownloadTask,
    didFinishDownloadingTo location: URL)
{
  // 省略属性闭包回调的情况
    if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
        delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
    }
}
  • 这段代码典型的通过我们前面建立的绑定关系,通过每次代理回调的 task 找到相应的 Request 然后因为属性关系,直接拿出 Request 的属性delegate 来处理相关事务
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
    }
}
  • 文件下载完毕转移存储地址,文件处理相关 以及error情况
  • 上面就是最典型的繁重恶心操作,必然是不需要被SessionDelegate 所需要知道的!

我们从 SessionManager -> Request 这一过程明面上就是直接交付,但是背地都是通过代理响应联通的:SessionDelegate -> 具体的Request的delegate。看到这里,是不是感觉非常的爽!优秀的框架总是能够给你在思想方面带来很大的提升,我们平时开发的时候也会有很恶心的耦合度,通讯问题。那么想必 Alamofire 的设计思路肯定能够给你带来一定的启示!

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!