Alamofire(四)-- 请求Request

通过前面对Alamofire的一些了解和使用,我们可以看到,在整个框架中,围绕着Request设计了很多的特性,所以,Request请求是所有请求核心基础所在。那么接下来,我们就来剖析一下这个重要的模块Request

请求流程

首先,我们来看一下这段代码:

Alamofire.request("https://httpbin.org/get")

这是一段非常简单的网络请求的代码,我们来看一下这个Request中究竟干了些什么。进入源码查看:

@discardableResult
public func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    return SessionManager.default.request(
        url,
        method: method,
        parameters: parameters,
        encoding: encoding,
        headers: headers
    )
}

可以看到,request函数内部还调用了SessionManagerrequest方法,这就说明了网络请求的出发点是来自SessionManager,很明显,Alamofire.swift这个文件应该是最顶层的封装,然后调用下层文件SessionManager.swift。好,我们接着来看:

@discardableResult
    open func request(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil)
        -> DataRequest
    {
        var originalRequest: URLRequest?

        do {
            originalRequest = try URLRequest(url: url, method: method, headers: headers)
            let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
            return request(encodedURLRequest)
        } catch {
            return request(originalRequest, failedWith: error)
        }
    }

看到,该函数内部创建了一个Request对象,然后把参数编码到这个Request中,后又调用了内部的一个request函数,参数即为上边Request对象。来吧,接着来看:

@discardableResult
    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)
        }
    }
  • 这个函数的权限是open的,所以,是可以使用SessionManager.default.request来发起请求的,但是,参数是_ urlRequest: URLRequestConvertible
  • URLRequestConvertible协议的目的是对URLRequest进行自定义的转换,因此,在获得转换后的URLRequest后,需要用URLRequest生成task,这样才能发起网络请求,在Alamofire中,但凡是request开头的函数,默认的都是DataRequest类型,现在有了URLRequest还不够,还需要检测她能否生成与之相对应的task
  • 在上边的函数中,用到了DataRequest.RequestableRequestable其实一个结构体,他实现了TaskConvertible协议,因此,它能够用URLRequest生成与之相对应的task。接下来就初始化DataRequest,然后真正的开始发起请求。

OK,到这里,我们来简单看一下这个请求过程是怎样的:


请求过程.png

Request

请求依赖关系

Request类,主要用于发送网络请求、接收response、关联服务器返回的数据、管理task

首先,RequestDataRequestDownloadRequestUploadRequestStreamRequest的基类,我们来看看相关属性:

// MARK: Properties

    /// The delegate for the underlying task.
    open internal(set) var delegate: TaskDelegate {
        get {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            return taskDelegate
        }
        set {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            taskDelegate = newValue
        }
    }

    /// The underlying task.
    open var task: URLSessionTask? { return delegate.task }

    /// The session belonging to the underlying task.
    public let session: URLSession

    /// The request sent or to be sent to the server.
    open var request: URLRequest? { return task?.originalRequest }

    /// The response received from the server, if any.
    open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }

    /// The number of times the request has been retried.
    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()

这些属性就很简单了,都很好理解的,最重要的,我们来关注一下Request的初始化方法:

// MARK: Lifecycle

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

        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() }
    }

可以看到,需要发起一个Request请求,我们只需要一个task就OK了。

  • 第一个参数session,主要用于CustomStringConvertibleCustomDebugStringConvertible这两个协议的实现方法中获取特定的数据。
  • 第二个参数是requestTask,这是一个枚举类型,看一下:
enum RequestTask {
        case data(TaskConvertible?, URLSessionTask?)
        case download(TaskConvertible?, URLSessionTask?)
        case upload(TaskConvertible?, URLSessionTask?)
        case stream(TaskConvertible?, URLSessionTask?)
    }
  • 我们在初始化Request的时候,只需要传递requestTask这个枚举值,我们就得到了两个重要的数据:Request的类型和相对应的task。这一变成手法的运用,大大提高了代码的质量。
  • RequestTask枚举中和选项绑定的数据有两个,TaskConvertible表示原始的对象,该对象实现了TaskConvertible协议,能够转换成taskURLSessionTask是原始对象转换后的task。因此衍生出一种高级使用方法的可能性,可以自定义一个类,实现TaskConvertible协议,就能够操纵task的转换过程,很灵活。
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }

这一行代码给代理的queue添加了一个操作,队列是先进先出原则,但是可以通过isSuspended暂停队列内部的操作。

关于参数编码

首先,Alamofire支持的编码格式如下:

URLEncoding
JSONEncoding
PropertyListEncoding

看下如下代码:

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 这个方法中,里面进行递归参数,把 keyvalue 取出,然后进行了百分号编码,放进元组保存,形成参数对
  • 外面将元组加入数组中
  • map 映射 ($0)=\($1)
  • 元素之间键入一个分隔符号 &

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

总结

读的越多,写的越多,你就会发现,Alamofire中的函数的设计很厉害,不是一时半会能够全部串联的。
由于知识水平有限,如有错误,还望指出,最后,常规推荐Cooci老师的文章。

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

推荐阅读更多精彩内容