【Alamofire源碼解析】09 - ParameterEncoding

這個(gè)文件里面寫了三種參數(shù)編碼的方式:URLEncodingJSONEncodingPropertyListEncoding

1. 輔助類型

// 用枚舉列舉了請(qǐng)求方法,并且用大寫的形式指定了rawValue
public enum HTTPMethod: String {
    case options = "OPTIONS"
    case get     = "GET"
    case head    = "HEAD"
    case post    = "POST"
    case put     = "PUT"
    case patch   = "PATCH"
    case delete  = "DELETE"
    case trace   = "TRACE"
    case connect = "CONNECT"
}

// Dictionary類型的參數(shù)
public typealias Parameters = [String: Any]

2. ParameterEncoding協(xié)議

規(guī)定了如何把參數(shù)編碼到請(qǐng)求當(dāng)中。

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

3. URLEncoding

把參數(shù)直接編碼到URL中。

1). Destination

列舉了參數(shù)編碼到請(qǐng)求中的三種方式。

// methodDependent: 由請(qǐng)求方法自己決定肠虽,`GET`, `HEAD` 和 `DELETE`直接拼接到請(qǐng)求url中,其他方法放到HTTP body
// queryString: 直接拼接到請(qǐng)求url中
// httpBody: 放到HTTP body
public enum Destination {
    case methodDependent, queryString, httpBody
}

2). 屬性和初始化

// 編碼方式是methodDependent的URLEncoding實(shí)例
public static var `default`: URLEncoding { return URLEncoding() }

// 編碼方式是methodDependent的URLEncoding實(shí)例玛追,其實(shí)跟default是相同的
public static var methodDependent: URLEncoding { return URLEncoding() }

// 編碼方式是queryString的URLEncoding實(shí)例
public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) }

// 編碼方式是httpBody的URLEncoding實(shí)例
public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }

public let destination: Destination

public init(destination: Destination = .methodDependent) {
    self.destination = destination
}

3). 編碼

// 實(shí)現(xiàn)ParameterEncoding協(xié)議
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    // 如果沒有傳入?yún)?shù)税课,直接返回
    guard let parameters = parameters else { return urlRequest }
    
    // 把參數(shù)編碼到URL中
    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 { // 把參數(shù)編碼到HTTP body
        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
}

// 根據(jù)傳入的key和value,返回處理好的查詢字符串組件
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
    var components: [(String, String)] = []
    
    if let dictionary = value as? [String: Any] { // value是字典
        for (nestedKey, value) in dictionary {
            // 用遞歸再次處理key和value
            components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
        }
    } else if let array = value as? [Any] { // value是數(shù)組
        for value in array {
            // 用遞歸再次處理key和value
            components += queryComponents(fromKey: "\(key)[]", value: value)
        }
    } else if let value = value as? NSNumber { // value是NSNumber
        if value.isBool {
            // NSNumber其實(shí)是Bool類型痊剖,用1或者0代替
            components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }
    } else if let bool = value as? Bool { // value是Bool類型韩玩,用1或者0代替
        components.append((escape(key), escape((bool ? "1" : "0"))))
    } else {
        components.append((escape(key), escape("\(value)")))
    }
    
    return components
}

// 對(duì)字符串進(jìn)行百分號(hào)編碼。
// 下面這些分隔符保留原字符串:
// - General Delimiters: ":", "#", "[", "]", "@"
// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="


public func escape(_ string: String) -> String {
    let generalDelimitersToEncode = ":#[]@"
    let subDelimitersToEncode = "!$&'()*+,;="
    
    var allowedCharacterSet = CharacterSet.urlQueryAllowed
    allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
    
    var escaped = ""
    
    //==========================================================================================================
    //
    //  在iOS 8.1和8.2中陆馁,如果一次性對(duì)大量的中文字符進(jìn)行百分號(hào)編碼會(huì)造成crash找颓,所以進(jìn)行了分批處理
    //  (這個(gè)bug是國(guó)人發(fā)現(xiàn)的,具體可以查看這個(gè)issue: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
}

// 返回一個(gè)進(jìn)行了百分號(hào)編碼并且拼接好的字符串
private func query(_ parameters: [String: Any]) -> String {
    var components: [(String, String)] = []
    
    // `<`這個(gè)其實(shí)是一個(gè)其實(shí)是一個(gè)closure叮贩,意思是讓keys按照從小到大的順序重新排列
    for key in parameters.keys.sorted(by: <) {
        let value = parameters[key]!
        components += queryComponents(fromKey: key, value: value)
    }
    return components.map { "\($0)=\($1)" }.joined(separator: "&")
}

// 判斷是否要把參數(shù)編碼到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
    }
}

4. JSONEncoding

把參數(shù)以SJON類型編碼到請(qǐng)求體中击狮。

1). 屬性和初始化

// 默認(rèn)的JSONEncoding實(shí)例
public static var `default`: JSONEncoding { return JSONEncoding() }

// 用更好輸出效果的JSONEncoding實(shí)例
public static var prettyPrinted: JSONEncoding { return JSONEncoding(options: .prettyPrinted) }

public let options: JSONSerialization.WritingOptions

public init(options: JSONSerialization.WritingOptions = []) {
    self.options = options
}

2). 編碼

// 把字典編碼到請(qǐng)求體中
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    // 沒有參數(shù),直接返回
    guard let parameters = parameters else { return urlRequest }
    
    do {
        // 把參數(shù)序列化成JSON
        let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
        
        // 設(shè)置Content-Type
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
    }
    
    return urlRequest
}

// 其實(shí)根上面那個(gè)方法幾乎一樣益老,只是第二個(gè)參數(shù)類型不同
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    guard let jsonObject = jsonObject else { return urlRequest }
    
    do {
        // 把參數(shù)序列化成JSON
        let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
        
        // 設(shè)置Content-Type
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
    }
    
    return urlRequest
}

5. PropertyListEncoding

把參數(shù)以PropertyList類型編碼到請(qǐng)求體中彪蓬。

1). 屬性和初始化

// 默認(rèn)的PropertyListEncoding實(shí)例,以xml的形式序列化
public static var `default`: PropertyListEncoding { return PropertyListEncoding() }

// 以xm的形式序列化的PropertyListEncoding實(shí)例
public static var xml: PropertyListEncoding { return PropertyListEncoding(format: .xml) }

// 以二進(jìn)制的形式序列化的PropertyListEncoding實(shí)例
public static var binary: PropertyListEncoding { return PropertyListEncoding(format: .binary) }

public let format: PropertyListSerialization.PropertyListFormat
public let options: PropertyListSerialization.WriteOptions

public init(
    format: PropertyListSerialization.PropertyListFormat = .xml,
    options: PropertyListSerialization.WriteOptions = 0)
{
    self.format = format
    self.options = options
}

2). 編碼

// 實(shí)現(xiàn)ParameterEncoding協(xié)議
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    guard let parameters = parameters else { return urlRequest }
    
    do {
        // 把參數(shù)序列化成PropertyList
        let data = try PropertyListSerialization.data(
            fromPropertyList: parameters,
            format: format,
            options: options
        )
        
        // 設(shè)置Content-Type
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error))
    }
    
    return urlRequest
}

有任何問題捺萌,歡迎大家留言档冬!

歡迎加入我管理的Swift開發(fā)群:536353151,本群只討論Swift相關(guān)內(nèi)容。

原創(chuàng)文章捣郊,轉(zhuǎn)載請(qǐng)注明出處。謝謝慈参!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呛牲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子驮配,更是在濱河造成了極大的恐慌娘扩,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壮锻,死亡現(xiàn)場(chǎng)離奇詭異琐旁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猜绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門灰殴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掰邢,你說我怎么就攤上這事牺陶。” “怎么了辣之?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵掰伸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我怀估,道長(zhǎng)狮鸭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任多搀,我火速辦了婚禮歧蕉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘康铭。我一直安慰自己廊谓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布麻削。 她就那樣靜靜地躺著蒸痹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呛哟。 梳的紋絲不亂的頭發(fā)上叠荠,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音扫责,去河邊找鬼榛鼎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的者娱。 我是一名探鬼主播抡笼,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼黄鳍!你這毒婦竟也來了推姻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤框沟,失蹤者是張志新(化名)和其女友劉穎藏古,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忍燥,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拧晕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梅垄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厂捞。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖队丝,靈堂內(nèi)的尸體忽然破棺而出蔫敲,到底是詐尸還是另有隱情,我是刑警寧澤炭玫,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布奈嘿,位于F島的核電站,受9級(jí)特大地震影響吞加,放射性物質(zhì)發(fā)生泄漏裙犹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一衔憨、第九天 我趴在偏房一處隱蔽的房頂上張望叶圃。 院中可真熱鬧,春花似錦践图、人聲如沸掺冠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽德崭。三九已至,卻和暖如春揖盘,著一層夾襖步出監(jiān)牢的瞬間眉厨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工兽狭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留憾股,地道東北人鹿蜀。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像服球,于是被迫代替她去往敵國(guó)和親茴恰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理斩熊,服務(wù)發(fā)現(xiàn)往枣,斷路器,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 10,970評(píng)論 6 13
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,811評(píng)論 6 342
  • 官方文檔 初始化 Initialization是為準(zhǔn)備使用類座享,結(jié)構(gòu)體或者枚舉實(shí)例的一個(gè)過程婉商。這個(gè)過程涉及了在實(shí)例里...
    hrscy閱讀 1,136評(píng)論 0 1
  • 村上春樹小說《海邊的卡夫卡》中有一個(gè)人物叫中田,小時(shí)候本是學(xué)校里最優(yōu)秀的學(xué)生盯捌,卻在一場(chǎng)昏迷中失去了讀寫能力淳衙,腦袋也...
    靈芙醉客閱讀 613評(píng)論 0 2