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自定義編碼器,有點複雜,會放在下一篇中詳解
以上純屬我的理解,不免有誤,如發現有錯誤的地方,歡迎評論指出,將第一時間修改,很是感謝~