美文网首页我的Swift开发
IOS源码解析:Alamofire 5 核心

IOS源码解析:Alamofire 5 核心

作者: 时光啊混蛋_97boy | 来源:发表于2021-01-29 14:17 被阅读0次

    原创:知识点总结性文章
    创作不易,请珍惜,之后会持续更新,不断完善
    个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
    温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

    目录

    • 一、抓包工具
      • 1、Charles 抓包工具
      • 2、Fiddler Everywhere 抓包工具
    • 二、Alamofire 架构
      • 1、接口
      • 2、请求
      • 3、响应
      • 4、底层
      • 5、其他
    • 三、核心流程
    • 四、功能点解析
      • 1、AF
      • 2、Request
      • 3、HTTPMethod
      • 4、ParameterEncoding
      • 5、Validation
      • 6、DispatchQueue
      • 7、AFError
      • 8、DataResponse
    • Demo
    • 参考文献

    一、抓包工具

    1、Charles 抓包工具

    a、配置各种证书比较麻烦

    b、Charles抓包工具总是会疯狂报错(这个问题可以重装Charles抓包工具解决掉)

    c、而且还存在抓取的内容乱码显示问题

    这个问题解决起来比较麻烦,我差不多都快放弃使用这个工具了,突然却被我解决掉这个问题了,在这里画个圈圈诅咒软件的设计者,你这么复杂的配置让人怎么玩你?

    在电脑端安装并信任证书

    Charles中,设置ssl proxy Setting中的ssl proxying的代理网址,按图中填写即可,这一步非常重要,我就是设置了这一步后才没有乱码的

    设置抓包的网址和端口,设置为全部都抓

    大功告成,终于抓取到正常的内容了,留下了喜极而泣的泪水,没忍住想把创作者拖出去打的冲动。


    2、Fiddler Everywhere 抓包工具

    推荐使用简单明了的Fiddler Everywhere抓包工具,这是它的下载地址 Fiddler Everywhere,免费版本的就可以用得很顺溜了。

    界面整体看起来也比较干净简洁,而且安装好以后就可以直接使用,不用处理一大堆的配置问题。

    和其它抓包工具一样,Fiddler Everywhere默认也是只能抓取HTTP请求,需要通过下载证书或进行相关配置,才能正常拦截HTTPS请求,配置如下,信任证书并勾选捕捉。


    二、Alamofire 架构

    1、接口

    Alamofire.swift:api 声明
    // 全局静态变量
    public let AF = Session.default
    // 当前Alamofire版本
    let version = "5.4.1"
    

    2、请求

    Request.swift:请求类,用于构建请求
    public protocol RequestDelegate: AnyObject
    
    public class Request
    public class DataRequest: Request
    public final class DataStreamRequest: Request
    public class DownloadRequest: Request
    public class UploadRequest: DataRequest
    
    ParameterEncoding.swift:参数编码
    public protocol ParameterEncoder
    
    open class JSONParameterEncoder: ParameterEncoder
    open class URLEncodedFormParameterEncoder: ParameterEncoder
    
    MultipartFormData.swift:自定义表单类
    open class MultipartFormData 
    
    ServerTrustEvaluation.swift:服务器验证
    public protocol ServerTrustEvaluating
    
    open class ServerTrustManager 
    public final class DefaultTrustEvaluator: ServerTrustEvaluating
    public final class RevocationTrustEvaluator: ServerTrustEvaluating
    public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating
    public final class PublicKeysTrustEvaluator: ServerTrustEvaluating
    public final class CompositeTrustEvaluator: ServerTrustEvaluating
    public final class DisabledTrustEvaluator: ServerTrustEvaluating
    

    3、响应

    Response.swift:响应类,用于构建响应
    public struct DataResponse<Success, Failure: Error> 
    public struct DownloadResponse<Success, Failure: Error>
    
    Validation.swift:响应数据验证
    extension Request
    extension DataRequest
    extension DataStreamRequest
    extension DownloadRequest
    
    Result+Alamofire.swift:请求结果表示
    extension Result 
    
    AFError.swift:错误类型
    public enum AFError: Error
    public enum MultipartEncodingFailureReason
    public enum ParameterEncodingFailureReason
    public enum ParameterEncoderFailureReason
    public enum ResponseValidationFailureReason
    public enum ResponseSerializationFailureReason
    public enum ServerTrustFailureReason
    public enum URLRequestValidationFailureReason
    

    4、底层

    Session.swift:请求session的管理类,底层使用NSURLSession实现
    open class Session
    MARK: - DataRequest
    MARK: - DataStreamRequest
    MARK: - UploadRequest
    
    SessionDelegate.swift:请求Session的代理对象,主要实现NSURLSession的代理方法以及回调闭包
    open class SessionDelegate: NSObject 
    extension SessionDelegate: URLSessionDelegate
    extension SessionDelegate: URLSessionTaskDelegate
    extension SessionDelegate: URLSessionDataDelegate
    extension SessionDelegate: URLSessionDownloadDelegate
    
    DispatchQueue+Alamofire.swift:GCD扩展,增加了一个名为after的延迟调用方法
    extension DispatchQueue
    

    5、其他

    NetworkReachabilityManager.swift:网络状态监听类
    open class NetworkReachabilityManager 
    
    Notifications.swift:定义通知
    extension Notification
    extension NotificationCenter
    public final class AlamofireNotifications: EventMonitor
    

    三、核心流程

    这里并不会贴上源码里的大段代码,而是通过一个例子来进入到Alamofire中的各个模块,让读者更好理解。

    a、安全认证

    info.plist中添加App Transport Security Settings,再将Allow Arbitrary Loads修改为YES后控制台输出结果为如下:


    b、核心流程的范例
    // 淘宝的一个搜索api
    let url = "http://suggest.taobao.com/sug"
    // 对袜子进行搜索
    let parameters: [String: Any] = [
        "code" : "utf-8",
        "q" : "袜子"
    ]
    
    AF.request(url, method: .get, parameters: parameters)
        .validate(statusCode: [200])
        .responseData(queue: DispatchQueue.global())
        { (responseData) in
            switch responseData.result
            {
            case .success(let data):
              guard let jsonString = String(data: data, encoding: .utf8) else { return }
              print("json字符串:\(jsonString)")
            case .failure(let error):
              print("错误信息:\(error)")
            }
        }
    

    c、范例中的功能点解析
    • 通过Alamofirerequest开始调用
    • 传入url
    • 使用get方法
    • 传入parameters,并在encoding中定义了参数的编码方式
    • 这里并不需要headers
    • validate中传入需要验证的statusCode的数组
    • responseData方法里传入全局队列和回调的处理闭包completionHandler
    • 在闭包里对返回的数据进行判断,若返回成功,打印jsonString,若失败则打印错误

    d、输出结果
    json字符串:
    {"result":[["袜子女","988016.6933754483"],["袜子男","865386.2105082254"],["袜子女冬","662313.8682009963"],["袜子女中筒袜","390508.191495028"],["袜子男长袜","650152.593118986"],["袜子男冬","747552.2895411798"],["袜子女ins潮","286597.0935191857"],["袜子男纯棉","324485.1344208922"],["袜子女冬季 加绒","329439.1491313167"],["袜子男中筒","300330.61181601905"]]}
    

    四、功能点解析

    1、AF

    a、全局静态变量
    AF是最外层用于发起网络请求的静态变量,来自于全局静态变量Session.default
    • 降低使用门槛,将复杂的功能实现进行下沉
    • 提供单一接口来实现功能上的全面覆盖,没有多余代码,非常简洁
    • 方便编程者解读源码,探究SDK的封装思路
    public let AF = Session.default
    
    default是提供给全局使用的共享单例
    open class Session
    {
        public static let default = Session()
    }
    

    b、请求实际的调用者是URLSession
    通过使用 URLSession 来初始化 Session
    public init(session: URLSession,
                delegate: SessionDelegate,
                rootQueue: DispatchQueue,
                startRequestsImmediately: Bool = true,
                requestQueue: DispatchQueue? = nil,
                serializationQueue: DispatchQueue? = nil,
                interceptor: RequestInterceptor? = nil,
                serverTrustManager: ServerTrustManager? = nil,
                redirectHandler: RedirectHandler? = nil,
                cachedResponseHandler: CachedResponseHandler? = nil,
                eventMonitors: [EventMonitor] = [])
    
    通过使用URLSessionConfiguration来初始化 Session,最终还是使用了URLSession
    public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
                            delegate: SessionDelegate = SessionDelegate(),
                            rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
                            startRequestsImmediately: Bool = true,
                            requestQueue: DispatchQueue? = nil,
                            serializationQueue: DispatchQueue? = nil,
                            interceptor: RequestInterceptor? = nil,
                            serverTrustManager: ServerTrustManager? = nil,
                            redirectHandler: RedirectHandler? = nil,
                            cachedResponseHandler: CachedResponseHandler? = nil,
                            eventMonitors: [EventMonitor] = [])
    {
        let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: rootQueue, name: "org.alamofire.session.sessionDelegateQueue")
        let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
    
        self.init(session: session,
                  delegate: delegate,
                  rootQueue: rootQueue,
                  startRequestsImmediately: startRequestsImmediately,
                  requestQueue: requestQueue,
                  serializationQueue: serializationQueue,
                  interceptor: interceptor,
                  serverTrustManager: serverTrustManager,
                  redirectHandler: redirectHandler,
                  cachedResponseHandler: cachedResponseHandler,
                  eventMonitors: eventMonitors)
    }
    

    2、Request

    open func request(_ convertible: URLConvertible,
                      method: HTTPMethod = .get,
                      parameters: Parameters? = nil,
                      encoding: ParameterEncoding = URLEncoding.default,
                      headers: HTTPHeaders? = nil,
                      interceptor: RequestInterceptor? = nil,
                      requestModifier: RequestModifier? = nil) -> DataRequest
    
    open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest
    
    a、URLConvertible协议

    我们在例子里传入的urlString类型,也可以将它转成URL类型再传入,结果都正确。这是因为StringURL都遵循URLConvertible协议,通过实现协议里的asURL()方法,从StringURLURLComponents类型转换为URL,若失败则抛出为AFError的错误。

    public protocol URLConvertible
    {
        func asURL() throws -> URL
    }
    
    extension String: URLConvertible
    {
        public func asURL() throws -> URL
        {
            guard let url = URL(string: self) 
            return url
        }
    }
    
    extension URL: URLConvertible
    {
        public func asURL() throws -> URL { self }
    }
    
    extension URLComponents: URLConvertible
    {
        public func asURL() throws -> URL
        {
            // 如果转换 url 失败, 抛出一个异常
            guard let url = url else { throw AFError.invalidURL(url: self) }
            return url
        }
    }
    

    b、URLRequestConvertible协议

    负责URLRequest的转换,和上面类似,就不多介绍了。

    // 可以转换成 urlrequest 的协议
    public protocol URLRequestConvertible
    {
        // 返回一个 urlrequest, 如果有错, 可以抛出异常
        func asURLRequest() throws -> URLRequest
    }
    
    // 创建 urlRequest
    extension URLRequestConvertible
    {
        
        public var urlRequest: URLRequest? { try? asURLRequest() }
    }
    
    // 返回自身
    extension URLRequest: URLRequestConvertible
    {
        public func asURLRequest() throws -> URLRequest { self }
    }
    

    c、返回Request的子类
    ❶ 包括request、download、upload等方法都是传入所需参数,发起请求,再返回Request的子类
    open func request(_ convertible: URLConvertible,
                      method: HTTPMethod = .get,
                      parameters: Parameters? = nil,
                      encoding: ParameterEncoding = URLEncoding.default,
                      headers: HTTPHeaders? = nil,
                      interceptor: RequestInterceptor? = nil,
                      requestModifier: RequestModifier? = nil) -> DataRequest
    
    open func download(_ convertible: URLConvertible,
                       method: HTTPMethod = .get,
                       parameters: Parameters? = nil,
                       encoding: ParameterEncoding = URLEncoding.default,
                       headers: HTTPHeaders? = nil,
                       interceptor: RequestInterceptor? = nil,
                       requestModifier: RequestModifier? = nil,
                       to destination: DownloadRequest.Destination? = nil) -> DownloadRequest
    
    open func upload(_ data: Data,
                     to convertible: URLConvertible,
                     method: HTTPMethod = .post,
                     headers: HTTPHeaders? = nil,
                     interceptor: RequestInterceptor? = nil,
                     fileManager: FileManager = .default,
                     requestModifier: RequestModifier? = nil) -> UploadRequest 
    
    ❷ 返回给我们的Request,可以让我们控制请求的暂停,恢复,取消等

    请求状态

    fileprivate var mutableState = MutableState()
    
    public var state: State { mutableState.state }
    public var isInitialized: Bool { state == .initialized }// 初始化
    public var isResumed: Bool { state == .resumed }// 恢复请求
    public var isSuspended: Bool { state == .suspended }// 暂停请求
    public var isCancelled: Bool { state == .cancelled }// 取消请求
    public var isFinished: Bool { state == .finished }// 完成请求
    

    暂停请求

    public func suspend() -> Self
    {
        $mutableState.write
        { mutableState in
            // 如果不能暂停, 那么就跳过
            guard mutableState.state.canTransitionTo(.suspended) else { return }
            // 变更可变状态为暂停
            mutableState.state = .suspended
            // 在下层队列中更新暂停状态,didSuspend()在暂停完成时调用
            underlyingQueue.async { self.didSuspend() }
    
            guard let task = mutableState.tasks.last, task.state != .completed else { return }
            // 调用真正的网络请求URLSessionTask的suspend()方法
            task.suspend()
            // 在下层队列中更新暂停状态,didSuspendTask()在URLSessionTask暂停时调用
            underlyingQueue.async { self.didSuspendTask(task) }
        }
    
        return self
    }
    

    取消请求

    public func cancel() -> Self
    {
        $mutableState.write
        { mutableState in
            guard mutableState.state.canTransitionTo(.cancelled) else { return }
    
            mutableState.state = .cancelled
    
            underlyingQueue.async { self.didCancel() }
    
            // Resume to ensure metrics are gathered.
            task.resume()
            // 取消
            task.cancel()
            underlyingQueue.async { self.didCancelTask(task) }
        }
    
        return self
    }
    

    恢复请求

    public func resume() -> Self
    {
        $mutableState.write { mutableState in
            guard mutableState.state.canTransitionTo(.resumed) else { return }
    
            mutableState.state = .resumed
    
            underlyingQueue.async { self.didResume() }
    
            guard let task = mutableState.tasks.last, task.state != .completed else { return }
    
            task.resume()
            underlyingQueue.async { self.didResumeTask(task) }
        }
    
        return self
    }
    

    did方法,以恢复请求为例。

    func didResume()
    {
        dispatchPrecondition(condition: .onQueue(underlyingQueue))
    
        eventMonitor?.requestDidResume(self)
    }
    
    func didResumeTask(_ task: URLSessionTask)
    {
        dispatchPrecondition(condition: .onQueue(underlyingQueue))
    
        eventMonitor?.request(self, didResumeTask: task)
    }
    

    3、HTTPMethod

    a、HTTPMethod是个结构体
    public struct HTTPMethod: RawRepresentable, Equatable, Hashable
    {
        public static let connect = HTTPMethod(rawValue: "CONNECT")
        public static let delete = HTTPMethod(rawValue: "DELETE")
        public static let get = HTTPMethod(rawValue: "GET")
        public static let head = HTTPMethod(rawValue: "HEAD")
        public static let post = HTTPMethod(rawValue: "POST")
        ...
    
        public let rawValue: String
    
        public init(rawValue: String)
        {
            self.rawValue = rawValue
        }
    }
    

    b、打印HTTPMethod的值
    open func request(_ convertible: URLConvertible,
                      method: HTTPMethod = .get,
                      ...
    {
        print("打印HTTPMethod的枚举:\(method)")
        print("打印HTTPMethod的枚举关联值:\(method.rawValue)")
        ......
    }
    

    输出结果

    打印HTTPMethod的值:HTTPMethod(rawValue: "GET")
    打印HTTPMethod的原始值:GET
    

    c、会话配置
    ❶ Session的初始化方法中使用了默认的会话配置
    public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default....)
    
    ❷ 使用了系统提供的默认会话配置,并为其配置了默认的HTTP的头部信息
    public static var default: URLSessionConfiguration
    {
        let configuration = URLSessionConfiguration.default
        configuration.headers = .default
    
        return configuration
    }
    
    ❸ HTTP默认的头部信息包括User-Agent、Accept-Encoding、Accept-Language
    extension HTTPHeaders
    {
        public static let default: HTTPHeaders = [.defaultAcceptEncoding,
                                                    .defaultAcceptLanguage,
                                                    .defaultUserAgent]
    }
    
    public static let defaultAcceptEncoding: HTTPHeader = {
        let encodings: [String]
        encodings = ["br", "gzip", "deflate"]
        return .acceptEncoding(encodings.qualityEncoded())
    }()
    
    public static let defaultAcceptLanguage: HTTPHeader =
    {
        .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded())
    }()
    
    public static let defaultUserAgent: HTTPHeader =
    {
        let bundle = info?[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
        let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown"
        let appBuild = info?[kCFBundleVersionKey as String] as? String ?? "Unknown"
        let alamofireVersion = "Alamofire/\(version)"
        ...
        let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
        return .userAgent(userAgent)
    }
    
    ❹ 运行结果
    {
      "args": {},
      "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
        "Accept-Language": "en;q=1.0",
        "Host": "httpbin.org",
        "User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1",
        "X-Amzn-Trace-Id": "Root=1-600e58ba-22848fce5ae105571e598f1e"
      },
      "origin": "222.76.251.163",
      "url": "https://httpbin.org/get"
    }
    

    4、ParameterEncoding

    https://johnny:p4ssw0rd@www.example.com:443/script.ext;param=value?query=value#ref
    
    ==========url的组成=========
    
    scheme //https
    user //johnny
    password //p4ssw0rd
    host //www.example.com
    port //443
    path // /script.ext
    pathExtension //ext
    pathComponents //["/", "script.ext"]
    parameterString //param=value
    query //query=value
    fragment //ref
    
    encoding: ParameterEncoding = URLEncoding.default,
    

    如果我有一个参数字典,这个参数字典又是如何从客户端传递到服务器的呢?Alamofire中是这样实现的:URLEncoding编码方式会把参数直接拼接到URL中或通过requesthttpBody传值。JSONEncoding编码方式会把参数字典编码成JSONData后赋值给requesthttpBody


    a、ParameterEncoding协议
    • ParameterEncoding协议:是一个定义如何编码的协议,常会用到的URLEncodingJSONEncoding编码方式都遵循这个协议
    • encode函数:用来完成对传入的parameters的编码工作,把参数绑定到urlRequest之中
    • urlRequest参数:需要实现URLRequestConvertible协议,实现该协议的对象能够转换成URLRequest
    • parameters参数:类型为Parameters,也就是字典:public typealias Parameters = [String: Any]
    • 函数返回值类型为URLRequest:至于返回的urlRequest是不是之前的urlRequest,这个不一定。另一个比较重要的是该函数会抛出异常
    public protocol ParameterEncoding
    {
        func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
    }
    

    b、URLEncoding
    ❶ url-encoded编码方式
    • URLEncoding会生成一个使用url-encoded方式编码过的字符串,可以添加到url或是请求体中,至于使用何种方式取决于编码的目的地参数
    • http 头中的 Content-Type 字段会被设置为 application/x-www-form-urlencoded; charset=utf-8
    • 由于没有一个明确的规定如何编码一个集合,我们在这里约定,对于数组,我们会在名字后面加上一个中括号[] 如(foo[]=1&foo[]=2),对于字典,则在中括号中再加入键值,如foo[bar]=baz
    public struct URLEncoding: ParameterEncoding
    
    ❷ 编码后的参数位置:定义编码后的字符串是放到url还是请求体中
    • methodDependent:对于 .get.head.delete 请求,它会将已编码查询字符串应用到现有的查询字符串中;对于其他类型的请求,会将其设置为 HTTP body
    • queryString: 将编码字符串设置或追加到请求的 URL
    • httpBody:将编码字符串设置为 URLRequestHTTP body
    public enum Destination
    {
        case methodDependent
        case queryString
        case httpBody
    
        // 是否将编码字符串放到url中
        func encodesParametersInURL(for method: HTTPMethod) -> Bool
        {
            switch self
            {
            case .methodDependent: return [.get, .head, .delete].contains(method)
            case .queryString: return true
            case .httpBody: return false
            }
        }
    }
    
    ❸ ParameterEncoding 协议的实现:编码并设置 request对象
    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
    {
        // 获取 request
        var urlRequest = try urlRequest.asURLRequest()
        // 获取参数,如果没有参数,那么直接返回
        guard let parameters = parameters else { return urlRequest }
    
        // 获取请求方法,同时根据请求方法来判断是否需要编码参数到 url 中
        if let method = urlRequest.method, destination.encodesParametersInURL(for: method)// 直接编码到 url 中
        {
            // 获取 url
            guard let url = urlRequest.url else
            {
                throw AFError.parameterEncodingFailed(reason: .missingURL)
            }
    
            // 构建一个URLComponents对象,并在其中添加参数
            if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty
            {
                // 此处 map 是 optional 的map,如果 optionvalue 不为空,则会调用 map 内的闭包
                // 如果 url 中本来就有一部分参数了,那么就将新的参数附加在后面
                let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
            }
        }
        else// 这里是要添加到请求体中
        {
            // 如果请求头尚未设置 Content-Type
            if urlRequest.headers["Content-Type"] == nil
            {
                // 在请求头中设置编码格式
                urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
            }
            // 编码到请求体中
            urlRequest.httpBody = Data(query(parameters).utf8)
        }
    }
    
    ❹ 将参数编码为查询字符串

    可以看到URLEncoding方式是将parameters通过添加 & ,= 的方式拼接到url身后。

    private func query(_ parameters: [String: Any]) -> String
    {
        // 创建一个数组,这个数组中存放的是元组数据,元组中存放的是key和字符串类型的value
        var components: [(String, String)] = []
    
        // 遍历参数,对参数做进一步的处理,然后拼接到数组中
        for key in parameters.keys.sorted(by: <)
        {
            let value = parameters[key]!
    
            // key的类型是String,但value的类型是any
            // 也就是说value不一定是字符串,也有可能是数组或字典,因此针对value需要做进一步的处理
            components += queryComponents(fromKey: key, value: value)
        }
        // 把元组内部的数据用=号拼接,然后用符号&把数组拼接成字符串
        return components.map { "\($0)=\($1)" }.joined(separator: "&")
    }
    
    ❺ 根据value类型进行百分号转义

    key的类型是String,但value的类型是any,也就是说value不一定是字符串,也有可能是数组或字典,因此针对value需要做进一步的处理。

    public func queryComponents(fromKey key: String, value: Any) -> [(String, String)]
    {
        // 最终结果
        var components: [(String, String)] = []
        
        switch value
        {
        // 如果value依然是字典,那么键后面加上[key]再调用自身,也就是做递归处理
        case let dictionary as [String: Any]:
            for (nestedKey, value) in dictionary
            {
                components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
            }
        // 如果value是数组,通过遍历在键后面加上[]后依然调用自身
        // 把数组拼接到url中的规则是这样的:数组["a", "b", "c"]拼接后的结果是key[]="a"&key[]="b"&key[]="c"
        case let array as [Any]:
            for value in array
            {
                components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
            }
        // 如果value是NSNumber,要进一步判断这个NSNumber是不是表示布尔类型
        case let number as NSNumber:
            if number.isBool// bool 值的处理
            {
                components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
            }
            else
            {
                components.append((escape(key), escape("\(number)")))
            }
        // 如果value是Bool,转义后直接拼接进数组
        case let bool as Bool:
            components.append((escape(key), escape(boolEncoding.encode(value: bool))))
        // 其他情况,转义后直接拼接进数组
        default:
            components.append((escape(key), escape("\(value)")))
        }
        return components
    }
    
    ❻ 百分号转义

    上边函数中的key已经是字符串类型了,那么为什么还要进行转义的?这是因为在url中有些字符是不允许的,这些字符会干扰url的解析。:#[]@!$&'()*+,;=这些字符必须要做转义,而?/可以不用转义。转义的意思就是百分号编码。

    public func escape(_ string: String) -> String
    {
        // 使用了系统自带的函数来进行百分号编码
        string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
    }
    
    ❼ 进行验证

    将核心流程范例里的url变为使用URLEncoding对参数编码后的字符串,并且向parameternil,再进行请求依然会得到同样的结果。

    let url = "http://suggest.taobao.com/sug?code=utf-8&q=%E8%A2%9C%E5%AD%90"
    AF.request(url, method: .get, parameters: nil)
    

    输出结果为:

    json字符串:
    {"result":[["袜子女","981907.3641717125"],["袜子男","774851.6019127683"],["袜子女冬","627697.2976415411"],["袜子女中筒袜","350247.61393421463"],["袜子男长袜","615388.650406337"],["袜子男冬","703565.6863077434"],["袜子女ins潮","285066.1735027441"],["袜子男纯棉","303921.3111099697"],["袜子女冬季 加绒","335283.97906813805"],["袜子男中筒","331615.3393111639"]]}
    

    d、JSONParameterEncoder

    JSONEncoding的主要作用是把参数以JSON的形式编码到request之中,当然是通过requesthttpBody进行赋值的。JSONEncoding提供了两种处理函数,一种是对普通的字典参数进行编码,另一种是对字符串数组进行编码,处理这两种情况的函数基本上是相同的。

    ❶ 使用 json 编码参数
    public struct JSONEncoding: ParameterEncoding
    {
        // 使用默认参数构造
        public static var `default`: JSONEncoding { JSONEncoding() }
    
        // 让其拥有更好的展示效果
        public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }
    
        // JSON序列化的写入方式
        public let options: JSONSerialization.WritingOptions
        public init(options: JSONSerialization.WritingOptions = [])
        {
            self.options = options
        }
    }
    
    ❷ ParameterEncoding 协议的实现:将parameters转化为二进制放入httpBody里

    因此也很好理解,为什么get方法的安全性不如post方法,因为get方法将请求参数暴露在外,而post放在请求体内。

    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
    {
        var urlRequest = try urlRequest.asURLRequest()
    
        guard let parameters = parameters else { return urlRequest }
    
        do
        {
            // json 格式化数据
            let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
    
            // 如果 Content-Type 尚未设置
            if urlRequest.headers["Content-Type"] == nil
            {
                // 设置请求头的Content-Type
                urlRequest.headers.update(.contentType("application/json"))
            }
            // 加上请求体
            urlRequest.httpBody = data
        }
        catch
        {
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
        }
    
        return urlRequest
    }
    
    还有一个encode方法,基本同上一致,但是可以接受数组的 json
    public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest
    

    5、Validation

    .validate(statusCode: [200])
    
    a、响应码的认证

    validateDataRequest的扩展中增加的方法。我们这里先看对statusCode的认证。

    extension DataRequest
    {
    
        public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
    
        public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int
        {
            validate { [unowned self] _, response, _ in
                self.validate(statusCode: acceptableStatusCodes, response: response)
            }
        }
    }
    

    那么到底是如何认证的呢?这里我们再进入它的内部实现。

    fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
                                           response: HTTPURLResponse)
        -> ValidationResult
        where S.Iterator.Element == Int
    {
        // 若validate传入code包含response响应的code则认证成功
        if acceptableStatusCodes.contains(response.statusCode)
        {
            return .success(())
        }
        // 若不包括,则返回失败,抛出AFError
        else
        {
            let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
            return .failure(AFError.responseValidationFailed(reason: reason))
        }
    }
    

    b、错误验证

    acceptableStatusCodes为我们实际在validate中传入的statusCode数组,在例子中为[200]。将例子中的[200]变为[201]就会打印出抛出的错误,因为http的正确返回码为200,我们传入的数组不包含200,就会抛出错误。当然我们也可以在例子中不调用validate这样就不会有这些验证。

    错误信息:responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 200))
    

    c、响应内容的认证

    这里只介绍了statusCode响应码的认证,实际上validate里还有对contentType响应内容等的认证。

    public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String
    {
        validate { [unowned self] _, response, data in
            self.validate(contentType: acceptableContentTypes(), response: response, data: data)
        }
    }
    
    fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
                                           response: HTTPURLResponse,
                                           data: Data?)
        -> ValidationResult
        where S.Iterator.Element == String
    {
        guard let data = data, !data.isEmpty else { return .success(()) }
    
        return validate(contentType: acceptableContentTypes, response: response)
    }
    

    6、DispatchQueue

    a、responseData

    我们会把请求返回的处理放入queue所传入的队列中,若我们没有向queue传入队列,那么会默认把处理放入主队列中。范例子中我们选择将处理放入全局队列中。

    .responseData(queue: DispatchQueue.global())
    
    public func responseData(queue: DispatchQueue = .main...)
    

    b、DispatchQueue+Alamofire.swift

    可以顺便看一下 DispatchQueue+Alamofire.swift 文件,里面增加了一个名为after的延迟调用方法,没有添加public关键字,那么说明只想在Alamofire内部调用,而不想暴露给我们。

    extension DispatchQueue
    {
        func after(_ delay: TimeInterval, execute closure: @escaping () -> Void)
        {
            asyncAfter(deadline: .now() + delay, execute: closure)
        }
    }
    

    c、Request.swift
    // 所有内部异步操作的串行队列
    public let underlyingQueue: DispatchQueue
    // 用于所有序列化操作的队列。默认情况下,它是一个以 underlyingQueue 为目标的串行队列
    public let serializationQueue: DispatchQueue
    
    init(id: UUID = UUID(),
         underlyingQueue: DispatchQueue,
         serializationQueue: DispatchQueue,
         eventMonitor: EventMonitor?,
         interceptor: RequestInterceptor?,
         delegate: RequestDelegate)
    {
        self.id = id
        self.underlyingQueue = underlyingQueue
        self.serializationQueue = serializationQueue
        self.eventMonitor = eventMonitor
        self.interceptor = interceptor
        self.delegate = delegate
    }
    

    7、AFError

    a、AFDataResponse
    .responseData(queue: DispatchQueue.global())
    { (responseData) in
        switch responseData.result
        {
        case .failure(let error):
          print("错误信息:\(error)")
        }
    }
    
    public func responseData(completionHandler: @escaping (AFDataResponse<Data>) -> Void)
    public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
    

    b、AFError的类型

    Alamofire里的错误都已经被定义到AFError中,我们打开 AFError.swift。在下面列举的AFError的类型中,除了invalidURL的参数是遵循协议URLConvertible,其余类型的参数又由枚举类型组成。这样就将很多种的错误类型,包括在了这5种类型中。

    //这里为了看得方便,将AFError重新整理了下,源码中的内容要更多,但类型是一样的
    enum AFError 
    {     
      case invalidURL(url: URLConvertible) //无效的URL 
      case parameterEncodingFailed(reason: ParameterEncodingFailureReason) //请求参数编码失败 
      case multipartEncodingFailed(reason: MultipartEncodingFailureReason) //多部分编码失败 
      case responseValidationFailed(reason: ResponseValidationFailureReason) //响应验证失败 
      case responseSerializationFailed(reason: ResponseSerializationFailureReason) //响应序列化失败 
    }
    

    c、localizedDescription

    AFError.swift里面还有很多内容,例如对localizedDescription的实现,在请求抛出错误时,我们打印error.localizedDescription看到的错误信息就是在这里定义的。

    // 请求参数编码失败
    extension AFError.ParameterEncodingFailureReason
    {
        var localizedDescription: String
        {
            switch self
            {
            case .missingURL:
                return "URL request to encode was missing a URL"
            case let .jsonEncodingFailed(error):
                return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
            case let .customEncodingFailed(error):
                return "Custom parameter encoder failed with error: \(error.localizedDescription)"
            }
        }
    }
    

    8、DataResponse

    .responseData(queue: DispatchQueue.global())
    { (responseData) in
    
    a、Any类型

    假如说序列化后的数据是data,最直接的想法就是把data设置为Any类型,在实际用到的时候在进行判断,这也是最普通的一种开发思维。现在我们就要打破这种思维。我们需要封装一个对象,这个对象能够表达任何结果,这就用到了swift中的泛型。

    public func responseJSON(queue: DispatchQueue = .main,
                             dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
                             emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
                             emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
                             options: JSONSerialization.ReadingOptions = .allowFragments,
                             completionHandler: @escaping (AFDataResponse<Any>) -> Void) -> Self
    

    b、Result<Any>

    上边的这个函数的主要目的是把请求成功后的结果序列化为JSONcompletionHandler函数的参数类型为DataResponse<Any>,其中的Any就会传递给Result,也就是Result<Any>

    public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
    public struct DataResponse<Success, Failure: Error>
    {
        public let result: Result<Success, Failure>
    }
    
    @frozen public enum Result<Success, Failure> where Failure : Error
    {
        /// A success, storing a `Success` value.
        case success(Success)
    
        /// A failure, storing a `Failure` value.
        case failure(Failure)
    }
    

    c、jsonObject

    那么问题来了,不是把数据解析成JSON了吗?为什么要返回Any类型呢?json本质上很类似于JavaScript中的对象和数组。JSONSerialization.jsonObject返回的类型是Any,这是因为解析后的数据有可能是数组,也有可能是字典。当然如果不是这两种格式的数据,使用JSONSerialization.jsonObject解析会抛出异常。

    // 字典
    {
        "people":
        [
            {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
            {"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
            {"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
        ]
    }
    
    // 数组
    [
        "a",
        "b",
        "c"
    ]
    

    d、打印信息

    为了能够打印更加详细的信息,又使DataResponse实现了CustomStringConvertibleCustomDebugStringConvertible协议。

    description
    extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible
    {
        public var description: String
        {
            "\(result)"
        }
    }
    
    debugDescription
    extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible
    {
        public var debugDescription: String
        {
            guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" }
    
            let requestDescription = DebugDescription.description(of: urlRequest)
    
            let responseDescription = response.map { response in
                let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers)
    
                return """
                \(DebugDescription.description(of: response))
                    \(responseBodyDescription.indentingNewlines())
                """
            } ?? "[Response]: None"
    
            let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None"
    
            return """
            \(requestDescription)
            \(responseDescription)
            [Network Duration]: \(networkDuration)
            [Serialization Duration]: \(serializationDuration)s
            [Result]: \(result)
            """
        }
    }
    

    续文见下篇:IOS源码解析:Alamofire 5 功能模块


    Demo

    Demo在我的Github上,欢迎下载。
    SourceCodeAnalysisDemo

    参考文献

    相关文章

      网友评论

        本文标题:IOS源码解析:Alamofire 5 核心

        本文链接:https://www.haomeiwen.com/subject/sqpqtltx.html