Alamofire源码学习目录合集linux
这俩都是用来在建立请求时,对参数进行编码用的,传入的参数ios
首先定义了Parameters别名为[String: Any], 只能用来编码字典参数, 协议很简单,只有一个方法用来把参数编码到URLRequest中,并返回新的URLRequest:json
/// A dictionary of parameters to apply to a `URLRequest`.
public typealias Parameters = [String: Any]
/// A type used to define how a set of parameters are applied to a `URLRequest`.
public protocol ParameterEncoding {
/// 使用URLRequestConvertible建立URLRequest, 而后把字典参数编码进URLRequest中, 能够抛出异常, 抛出异常时会返回AFError.parameterEncodingFailed错误
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
复制代码
Alamofire提供了默认实现,分别用来编码url query string跟jsonswift
例子: key: [value1, value2]
1.使用key后面跟方括号而后跟等号跟值,例如: key[]=value1&key[]=value2
2.key后面不跟括号, 例如: key=value1&key=value2数组
key[subkey1]=value1&key[subkey2]=value2markdown
代码注释:闭包
public struct URLEncoding: ParameterEncoding {
// MARK: 辅助数据类型
/// 定义参数被编码到url query中仍是body中
public enum Destination {
/// 有method决定(get, head, delete为urlquery, 其余为body)
case methodDependent
/// url query
case queryString
/// body
case httpBody
/// 返回是否要把参数编入到url query中
func encodesParametersInURL(for method: HTTPMethod) -> Bool {
switch self {
case .methodDependent: return [.get, .head, .delete].contains(method)
case .queryString: return true
case .httpBody: return false
}
}
}
/// 决定如何编码Array
public enum ArrayEncoding {
/// key后跟括号编码
case brackets
/// key后不跟括号编码
case noBrackets
/// 对key进行编码
func encode(key: String) -> String {
switch self {
case .brackets:
return "\(key)[]"
case .noBrackets:
return key
}
}
}
///决定如何编码Bool
public enum BoolEncoding {
/// 数字: 1, 0
case numeric
/// string: true, false
case literal
/// 对值进行编码
func encode(value: Bool) -> String {
switch self {
case .numeric:
return value ? "1" : "0"
case .literal:
return value ? "true" : "false"
}
}
}
// MARK: 快速初始化的三个静态计算属性
/// 默认使用method决定编码位置, 数组使用带括号, bool使用数字
public static var `default`: URLEncoding { URLEncoding() }
/// url query 编码, 数组使用带括号, bool使用数字
public static var queryString: URLEncoding { URLEncoding(destination: .queryString) }
/// form 表单编码到body, 数组使用带括号, bool使用数字
public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) }
//MARK: 属性与初始化
/// 参数编码位置
public let destination: Destination
/// 数组编码格式
public let arrayEncoding: ArrayEncoding
/// Bool编码格式
public let boolEncoding: BoolEncoding
public init(destination: Destination = .methodDependent, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric) {
self.destination = destination
self.arrayEncoding = arrayEncoding
self.boolEncoding = boolEncoding
}
// MARK: 实现协议的编码方法
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
//先拿到URLRequest
var urlRequest = try urlRequest.asURLRequest()
//没参数的话直接返回
guard let parameters = parameters else { return urlRequest }
//先拿到method, 而后使用method判断下往哪里编码参数
//不够严谨, 若是method为空, 应该抛出异常的. ParameterEncoder中有处理
if let method = urlRequest.method, destination.encodesParametersInURL(for: method) {
//url query编码
guard let url = urlRequest.url else {
// url为空直接抛出异常
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
//先获取到已有的query string, 存在的话就加上个&, 而后拼接上新的query string
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
//body编码
if urlRequest.headers["Content-Type"] == nil {
//设置Content-Type
urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
}
//把query string转成utf8编码丢入body中
urlRequest.httpBody = Data(query(parameters).utf8)
}
return urlRequest
}
/// 对key-value对进行编码, value主要处理字典,数组,nsnumber类型的bool,bool以及其余值
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
switch value {
case let dictionary as [String: Any]:
//字典处理, 遍历字典递归调用
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
case let array as [Any]:
for value in array {
//数组处理, 根据数组key编码的类型遍历递归调用
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
case let number as NSNumber:
//nsnumber使用objCType类判断是不是bool
if number.isBool {
components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
} else {
components.append((escape(key), escape("\(number)")))
}
case let bool as Bool:
//bool处理, 根据编码类型来处理
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
default:
//其余的,直接转成string
components.append((escape(key), escape("\(value)")))
}
return components
}
/// url转义, 转成百分号格式的
/// 会忽略 :#[]@!$&'()*+,;=
public func escape(_ string: String) -> String {
string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
}
/// 把参数字典转成query string
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-value对进行编码
components += queryComponents(fromKey: key, value: value)
}
//拼接成query string返回
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
}
复制代码
使用JSONSerialization来把参数字典编码为json, 必定会被编码到body中, 而且会设置Content-Type为application/jsonapp
代码注释:post
public struct JSONEncoding: ParameterEncoding {
// MARK: 用来快速初始化的静态计算变量
//默认类型, 压缩json格式
public static var `default`: JSONEncoding { JSONEncoding() }
//标准json格式
public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }
// MARK: 属性与初始化
//保存JSONSerialization.WritingOptions
public let options: JSONSerialization.WritingOptions
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
// MARK: 实现协议的编码方法
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
//拿到Request
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
//编码成data
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
//设置Content-Type
if urlRequest.headers["Content-Type"] == nil {
urlRequest.headers.update(.contentType("application/json"))
}
//丢入body
urlRequest.httpBody = data
} catch {
//解析json出错就抛出错误
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
//把json对象编码进body中, 其实上面的编码方法能够直接掉这个方法, 两个方法实现一毛同样
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let jsonObject = jsonObject else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
if urlRequest.headers["Content-Type"] == nil {
urlRequest.headers.update(.contentType("application/json"))
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
复制代码
extension NSNumber {
fileprivate var isBool: Bool {
// Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of
// swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22).
String(cString: objCType) == "c"
}
}
复制代码
协议很简单,也是只有一个方法,把Parameters类型的参数编码进URLRequest中,可是要求Parameters类型必须符合Encodable协议。学习
public protocol ParameterEncoder {
func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest
}
复制代码
其实有不少地方相似ParameterEncoding,也是把参数编码编码进Request,编码位置也是能够控制,可是对参数要求不一样:
ParameterEncoding要求参数是字典类型,字典的value是Any的,编码为url query string时会直接强制转成String,所以对于标准类型之外的数据,编码出来的值就会错误。编码为JSON时,标准类型之外的数据,会致使编码错误,抛出异常
ParameterEncoder要求参数符合Encodable协议,编码时使用的是Encoder协议对象,编码为json时,用的是JSONEncoder,编码为url query string时,用的是本身实现的URLEncodedFormEncoder编码器
所以,若编码的参数为符合Encodable类型的字典时,使用两种编码方式都ok。好比parameter = ["a": 1, "b": 2]
这样的参数。
也有两个默认实现,分别用来进行json编码与url query string编码:
使用系统的JSONEncoder来编码数据,能够控制json的格式,ios11以上还支持根据key来排序(json字典为无序),实现方法也是比较简单:
open class JSONParameterEncoder: ParameterEncoder {
//MARK: 用来快速建立对象的静态计算属性
/// 默认类型, 使用默认的JSONEncoder初始化, 会压缩json格式
public static var `default`: JSONParameterEncoder { JSONParameterEncoder() }
/// 使用标准json格式输出
public static var prettyPrinted: JSONParameterEncoder {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
return JSONParameterEncoder(encoder: encoder)
}
/// ios11以上支持输出的json根据key排序
@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
public static var sortedKeys: JSONParameterEncoder {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
return JSONParameterEncoder(encoder: encoder)
}
// MARK: 属性与初始化
/// 用来编码参数的JSONEncoder
public let encoder: JSONEncoder
public init(encoder: JSONEncoder = JSONEncoder()) {
self.encoder = encoder
}
/// 实现协议的编码方法:
open func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest {
//获取参数
guard let parameters = parameters else { return request }
var request = request
do {
//把参数编码成json data
let data = try encoder.encode(parameters)
//丢入body
request.httpBody = data
//设置Content-Type
if request.headers["Content-Type"] == nil {
request.headers.update(.contentType("application/json"))
}
} catch {
//解析json异常就抛出错误
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return request
}
}
复制代码
url编码, 使用Destination来判断编码到url query仍是body中, 编码数据使用的是URLEncodedFormEncoder类
open class URLEncodedFormParameterEncoder: ParameterEncoder {
/// 参数编码位置,与ParameterEncoding同样
public enum Destination {
case methodDependent
case queryString
case httpBody
func encodesParametersInURL(for method: HTTPMethod) -> Bool {
switch self {
case .methodDependent: return [.get, .head, .delete].contains(method)
case .queryString: return true
case .httpBody: return false
}
}
}
// MARK: 默认初始化对象, 使用URLEncodedFormEncoder默认参数, 编码位置由method决定
public static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
/// 用来编码数据的URLEncodedFormEncoder对象
public let encoder: URLEncodedFormEncoder
/// 编码位置
public let destination: Destination
public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) {
self.encoder = encoder
self.destination = destination
}
// 实现协议的编码参数方法
open func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest {
//获取参数
guard let parameters = parameters else { return request }
var request = request
//首先要保证url存在
guard let url = request.url else {
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
}
//而后保证method存在
guard let method = request.method else {
let rawValue = request.method?.rawValue ?? "nil"
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue)))
}
//根据编码位置, 进行编码操做
if destination.encodesParametersInURL(for: method),
var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
//url query
//这里格式化了下写法写下详细注释(吹爆swift!)
let query: String = try Result<String, Error> {//初始化Request(参数为能够抛出异常的闭包)
try encoder.encode(parameters)//编码参数
}
.mapError {//编码出错转换为AFError
AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0))
}
.get()//get能够获取成功数据, 若为error, 会抛出异常
//这里写法也很骚, 把原querystring与新的querystring组合成一个[String?]数组, 而后compactMap去掉nil, 再用&组合起来
let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands()
components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString
//url不能为空
guard let newURL = components.url else {
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
}
request.url = newURL
} else {
//设置Content-Type
if request.headers["Content-Type"] == nil {
request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
}
//编码, 而后丢入body 吐槽:尾随闭包+写一行读起来太难受了
request.httpBody = try Result<Data, Error> {
try encoder.encode(parameters)
}
.mapError {
AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0))
}
.get()
}
return request
}
}
复制代码
这个URLEncodedFormEncoder自定义编码器,有点复杂,会放在下一篇中详解
以上纯属我的理解,不免有误,如发现有错误的地方,欢迎评论指出,将第一时间修改,很是感谢~