Alamofire(3)-Request解读

2019-08-22  本文已影响0人  BoxJing

前面一篇文章分析到了最上面的一层SessionManager,本篇开始分析一下里面的一层Request。

进入SessionManager里面的request方法:

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

有个do...catch语句,基本上不会遇到,但是这么牛逼的框架必须考虑到二货随便传参进来!除非像我这么干,的确会干到catch里面:

Alamofire.request("urlStr………………")
            .responseJSON { (response) in
                print(response)
        }
请求连接错误.png
do语句里面第一句就是个很简单的原始请求初始化保存起来先,第二句有个encoding.encode方法,虽然入参是ParameterEncoding类型,但是默认值是一个URLEncoding的类型,所以直接干进URLEncoding.encode方法,进行进一步的分析。
encode.png
直接上源码:
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
    }

这么一坨代码if...else...语句占绝大部分,我们直接看这个判断语句,if里有一个encodesParametersInURL方法,字面意思就是编码参数到url里面,干进去看看是个什么东西:

private func encodesParametersInURL(with method: HTTPMethod) -> Bool {
        switch destination {
        case .queryString:
            return true
        case .httpBody:
            return false
        default:
            break
        }

        switch method {
        case .get, .head, .delete:
            return true
        default:
            return false
        }
    }

也就是说GET/DELETE/HEAD方法的请求,是需要将参数直接拼接在请求连接的后面的。我们实实在在来个GET请求:

let urlStr = "http://3g.163.com/touch/reconstruct/article/list/BA10TA81wangning/10-2.html"
Alamofire.request(urlStr, parameters: ["type":"1","page":"1"])
            .responseJSON { (response) in
                print(response)
        }

encode方法里打上断点会发现如下图:

请求参数拼接.png
再来个特殊点的:
let urlStr = "http://3g.163.com/touch/reconstruct/article/list/BA10TA81wangning/10-2.html?name=box"
Alamofire.request(urlStr, parameters: ["type":"1","page":"1"])
            .responseJSON { (response) in
                print(response)
        }
连接里带参的其他拼接.png
相信已经清楚了,这个拼接就是这么好用!进入这个query方法,瞄一眼:
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: "&")
    }

joined(separator: "&")熟悉不熟悉,就是在每两项中间插入一个&,里面有一个queryComponents方法,干进去看一看:

public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
        var components: [(String, String)] = []

        if let dictionary = value as? [String: Any] {
            for (nestedKey, value) in dictionary {
                components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
            }
        } else if let array = value as? [Any] {
            for value in array {
                components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
            }
        } else if let value = value as? NSNumber {
            if value.isBool {
                components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
            } else {
                components.append((escape(key), escape("\(value)")))
            }
        } else if let bool = value as? Bool {
            components.append((escape(key), escape(boolEncoding.encode(value: bool))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }

        return components
    }

看到了很骚的递归操作queryComponents,因为参进来的参数是个对象,鬼知道是什么类型,所以需要一层一层的转换到最终的一个元组类型,里面的escape又是个什么鬼,干进去:

    public func escape(_ string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="

        var allowedCharacterSet = CharacterSet.urlQueryAllowed
        allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

        var escaped = ""

        //==========================================================================================================
        //
        //  Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
        //  hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
        //  longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
        //  info, please refer to:
        //
        //      - https://github.com/Alamofire/Alamofire/issues/206
        //
        //==========================================================================================================

        if #available(iOS 8.3, *) {
            escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
        } else {
            let batchSize = 50
            var index = string.startIndex

            while index != string.endIndex {
                let startIndex = index
                let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
                let range = startIndex..<endIndex

                let substring = string[range]

                escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? String(substring)

                index = endIndex
            }
        }

        return escaped
    }

里面重要的就是CharacterSet.urlQueryAllowedaddingPercentEncoding最终出来的就是经过百分号编码的字符串。最后来个完美的视图:

数组里的元组拼接.png
那么除了GET/HEAD/DELETE方法其他的请求方法是如何做的?直接看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)

很明显是将百分号编码后的参数进行转换成data赋值给request的httpBody即可。到此可以正式进入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)
        }
    }

看到DataRequest.Requestable这样的操作,第一反应是这个类的静态方法,进去看一眼:

open class DataRequest: Request {

    // MARK: Helper Types

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

明显是个结构体, urlRequest就是结构体构造的时候传进去的参数,这个结构体内部包含一个返回URLSessionTask的task方法,内部同步创建返回一个URLSessionDataTask对象。

let request = DataRequest(session: session, requestTask: .data(originalTask, task))

看到这个代码,直接干进去DataRequest找它的初始化方法,没有!!!找父类Request的初始化方法:

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

这个RequestTask类型是个枚举,里面的值看名字就能想到是干嘛用的,就是这么通俗易懂,数据/下载/上传/数据流:

enum RequestTask {
        case data(TaskConvertible?, URLSessionTask?)
        case download(TaskConvertible?, URLSessionTask?)
        case upload(TaskConvertible?, URLSessionTask?)
        case stream(TaskConvertible?, URLSessionTask?)
    }

这种通过枚举的方式来提供接口的标识方法,可以方便的根据不同的标识走不同的逻辑,在我们日常的开发中的确有太多的地方可以这么干。

delegate[task] = request

这句代码是不是让人很费解,这种操作有什么意义?看着像数组的赋值或者是字典的赋值,进入SessionDelegate的源码找一找:

/// Access the task delegate for the specified task in a thread-safe manner.
    open subscript(task: URLSessionTask) -> Request? {
        get {
            lock.lock() ; defer { lock.unlock() }
            return requests[task.taskIdentifier]
        }
        set {
            lock.lock() ; defer { lock.unlock() }
            requests[task.taskIdentifier] = newValue
        }
    }

用这种Swift下标的方式进行赋值和取值,让人觉得非常清晰,如果想从SessionDelegate对象里面拿到request,直接拿比较困难,但是从SessionDelegate对象里面拿到task就很容易,那么这样一赋值,再通过task拿到这个request就方便很多。来个图证明一下:

通过task找到Request.png

疑问来了,为毛不直接这里实现掉?直接点进去看一看这个方法里面的具体东西:

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

这么一坨文件操作,你让一个大佬级别的来做合适吗?不合适!直接交给干苦力的活就可以了。图片中第一个if判断是判断用户在外面有没有自己的处理回调闭包downloadTaskDidFinishDownloadingToURL,如果有,就直接交给用户处理,没有的话框架自己处理掉。

到此,本篇主要分析了Request的参数处理,以及SessionDelegate和TaskDelegate的关系。解析在一步一步的深入,一步一步的细化:

细化思想.png
上一篇 下一篇

猜你喜欢

热点阅读