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,到这里,我们来简单看一下这个请求过程是怎样的:


Alamofire(四)-- 请求Request_第1张图片
请求过程.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老师的文章。

你可能感兴趣的:(Alamofire(四)-- 请求Request)