Alamofire (3)—— Request

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


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


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

一、SesssionManager的总结

  • 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度仰望天空,该死!我这无处安放的魅力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容