Alamofire-从一个简单的请求深入源代码(1)

今天我们从一个简单的例子中, 来一步一步剖析 Alamofire 的源代码.

简单的例子

例子很简单, 就是请求一个api, 然后打印输出结果而已.

Alamofire.request("https://httpbin.org/get").responseString { (response) in
    if let string = response.result.value {
        print(string)
    }
}

Alamofire.request

你如果自己写这段代码就知道, 你在写Alamofire 的时候, 是没有代码提示的, 这并不是一个 bug, 因为 Alamofire 并不是一个类型, 而是一个模块名, 在这里只是起到一个类似命名空间的作用. 所以你大可将例子改为request(xxx)...

request 函数签名

request 函数其实并不只有一个参数, 我们这里其他的参数都是使用的默认值而已.
完成的函数签名

func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest

这里一下出现了很多未知的类型, 我们慢慢来.

URLConvertible

我们在示例中明明是传入的一个字符串, 为何这里是一个URLConvertible 类型? 想要回答这个问题, 我们不妨看一下定义

public protocol URLConvertible {
    func asURL() throws -> URL
}

URLConvertible 只是一个协议, 这个协议也只有一个方法. 方法的目的也很简单--想办法变成一个URL.
同时, String 类型也是实现了这个协议的(为了方便阅读, 稍微调整了一下代码缩进)

extension String: URLConvertible {
    public func asURL() throws -> URL {
        guard let url = URL(string: self) else {
            throw AFError.invalidURL(url: self) 
        }
        return url
    }
}

几行代码, 一目了然, 无需多言, 错误处理也很到位.
String 类似, URL, URLComponents 也是实现了这个协议的. 同时, 如果你愿意, 你也可以自己实现, 例如你想把一个枚举转换成 url, 这样请求 api, 就可以直接填写枚举, 岂不美哉?

HTTPMethod

这个就不难理解, 请求方法而已, 这里默认的方法是GET, 除此之外, 还有"POST", "PUT" 等等. 如果你想对 http 请求方法有更深入了解, 可以查阅 HTTP 权威指南

Parameters

这个类型只是一个别名, 定义如下
public typealias Parameters = [String: Any]
你可以在这里填入你要请求的参数.

ParameterEncoding

参数的编码方式.
常用的编码方式为URLEncoding, 这种编码方式, 会将参数编码为 param1=value1&param2=value2 类似的结构.
初次之外, 还有编码为 JSON, PLIST 方式.
ParameterEncoding 本身是一个枚举, 定义如下

public protocol ParameterEncoding {
    func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}

可以看出来, 目的很简单, 就是想要根据参数和请求(URLRequestConvertible, 这个我们后面来讲, 这里你就认为他是一个URLRequest 吧)生成一个含有参数的 URLRequest
上面说到, Alamofire 为我们已经预定义了三种编码方式, 我们先来看 URLEncoding.

URLEncoding

编码的最终格式如上面所示, 而编码后的数据放在那里, 则有两种情况, 分别是直接放在 URL 上和放在请求体中( http body).
而具体放在那里, Alamofire 给我们两种方案, 一种是自动根据请求方法选择, 会根据 HTTP 请求方法来确定, 例如针对 GET 请求, 通常是直接放在 URL 中.第二种是手动制定
为此, Alamofire 在 URLEncoding 类型内部, 定义了一个结构体, 同时, 定了了几个便捷属性, 方便外部使用.

public struct URLEncoding: ParameterEncoding {
    public enum Destination {
        case methodDependent, queryString, httpBody
    }
   /// 依赖方法 
    public static var `default`: URLEncoding { return URLEncoding() }
    /// 与 default 相同
    public static var methodDependent: URLEncoding { return URLEncoding() }
    /// 编码时, 参数放到 url 中
    public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) }
    /// 编码时, 参数放到请求体中
    public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }
    ///编码后的参数位置
    public let destination: Destination
    /// 构造函数
    public init(destination: Destination = .methodDependent) {
        self.destination = destination
    }
    ....
}

接下来我们来看看核心的编码功能

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 = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
        // 直接编码到 url 中
        // 获取 url
        guard let url = urlRequest.url else {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }
        // 构建一个URLComponents 对象, 并在其中添加参数
        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
            // 此处 map 是 optional 的map, 如果 optionvalue 不会空的时候, 会调用 map 内的闭包
            // 如果 url 中本来就有一部分参数了, 那么就将新的参数附加在后面
            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")
        }
        // 编码到 body 中
        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
    }
    return urlRequest
}

注释我都写在代码中里面了.
其中这里面有用到几个函数, 一个是encodesParametersInURL
这个函数是来判断是否要编码参数到URL 中
实现如下

private func encodesParametersInURL(with method: HTTPMethod) -> Bool {
    /// 如果在编码位置中明确指出了, 那么就根据这个来确定是否要编码在 url 中
    switch destination {
    case .queryString:
        return true
    case .httpBody:
        return false
    default:
        break
    }
    /// 如果是依赖方法, 那么就根据请求的方法来决定
    switch method {
    case .get, .head, .delete:
        return true
    default:
        return false
    }
}

还有一个函数, query, 这个用于将参数编码. 这个很细节, 可以后面有时间单独讲讲. 这里就略过.
URLEncoding 就这些了, JSONEncoding, PropertyListEncoding 和这个类似, 只不过编码方式不同而已, 需要注意的是, 后面这两种, 都是直接编码到 HTTPBody 中的. 如果你想把 JSON 编码到 URL 中, 你就只有自己写一个, 你可以依葫芦画瓢, 也是很容易的.
接下来我们继续回到 Alamofire.request 函数中

HTTPHeaders

这个也是跟 Parameters 参数一样的, 一个别名而已
public typealias HTTPHeaders = [String: String]
你可以用这个参数, 在请求头中添加一些自定义的请求头.
讲到这里, 我们才把 Alamofire.request 的参数列表理了一遍, 接下来, 我们来看看函数体
另外, 我再 alamofire 中添加了一些中文注释, 愿意的话, 你也可看看https://github.com/ywwzwb/alamofire-chinese

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

推荐阅读更多精彩内容