在上一篇Alamofire源碼學習(九): ParameterEncoding與ParameterEncoder中有提到, ParameterEncoder協議用來把任何遵循Encodable協議的參數編碼添加到URLRequest當中,在默認實現URLEncodedFormParameterEncoder類中,編碼參數用的就是這個自定義實現的URLEncodedFormEncoder編碼器,用來把Encodable協議的參數編碼爲url query string,參數類型能夠是基本數據類型(Int,Double等)也能夠是其餘高級類型(Data,Date,Decimal等),也能夠是實現了Encodable協議的自定義類型。github
該類被修飾爲final,不容許繼承,只容許使用初始化參數控制編碼邏輯,其實URLEncodedFormEncoder類自己並無實現編碼方法,只是定義了N多編碼時行爲的定義,真正用來編碼的是內部類 _URLEncodedFormEncoder ,全部對參數的編碼處理都在該內部類中完成,編碼後的數據保存在URLEncodedFormComponent中,傳遞給上層URLEncodedFormParameterEncoder時,使用URLEncodedFormSerializer將編碼後的數據序列化爲url query string。算法
URLEncodedFormEncoder定義了4中數據類型的編碼格式,編碼時可自由選擇:swift
/// 數組編碼方式
public enum ArrayEncoding {
case brackets
case noBrackets
func encode(_ key: String) -> String {
switch self {
case .brackets: return "\(key)[]"
case .noBrackets: return key
}
}
}
複製代碼
/// Bool編碼方式
public enum BoolEncoding {
case numeric
case literal
func encode(_ value: Bool) -> String {
switch self {
case .numeric: return value ? "1" : "0"
case .literal: return value ? "true" : "false"
}
}
}
複製代碼
Data的延遲編碼方式爲:在自定義編碼時,若對Data的編碼方式是deferredToData類型,會建立一個子編碼器對Data進行編碼,會使用Data默認的編碼格式(UInt8數組)api
/// Data編碼方式
public enum DataEncoding {
/// 延遲編碼成Data
case deferredToData
/// base64字符串編碼
case base64
/// 使用閉包來編碼成自定義格式的字符串
case custom((Data) throws -> String)
/// 編碼data
func encode(_ data: Data) throws -> String? {
switch self {
case .deferredToData: return nil
case .base64: return data.base64EncodedString()
case let .custom(encoding): return try encoding(data)
}
}
}
複製代碼
Date的延遲編碼方式相似Data的,不過默認編碼格式是會編碼爲Double類型的距離1970.1.1的秒.毫秒數組
public enum DateEncoding {
/// 用來把Data轉換成ISO8601字符串的Formatter
private static let iso8601Formatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
return formatter
}()
/// 延遲編碼成Date
case deferredToDate
/// 編碼成從1910.1.1開始的秒字符串
case secondsSince1970
/// 編碼成從1910.1.1開始的毫秒秒字符串
case millisecondsSince1970
/// 編碼成ISO8601標準字符串
case iso8601
/// 使用自定義的格式器編碼
case formatted(DateFormatter)
/// 用閉包來自定義編碼Date
case custom((Date) throws -> String)
func encode(_ date: Date) throws -> String? {
switch self {
case .deferredToDate:
return nil
case .secondsSince1970:
return String(date.timeIntervalSince1970)
case .millisecondsSince1970:
return String(date.timeIntervalSince1970 * 1000.0)
case .iso8601:
return DateEncoding.iso8601Formatter.string(from: date)
case let .formatted(formatter):
return formatter.string(from: date)
case let .custom(closure):
return try closure(date)
}
}
}
複製代碼
Key的編碼方式是從系統的JSONEncoder.KeyEncodingStrategy與XMLEncoder.KeyEncodingStrategy共同派生出來的編碼方式主要是針對Key的字符串表現形式進行了定義:markdown
public enum KeyEncoding {
/// 默認格式,不編碼key
case useDefaultKeys
/// 駝峯轉下劃線蛇形: oneTwoThree -> one_two_three
case convertToSnakeCase
/// 駝峯轉串形: ontTwoThree -> one-two-three
case convertToKebabCase
/// 首字母大寫: oneTwoThree -> OneTwoThree
case capitalized
/// 所有轉爲大寫: oneTwoThree -> ONETWOTHREE
case uppercased
/// 所有轉爲小寫: oneTwoThree -> onetwothree
case lowercased
/// 使用閉包來自定義編碼規則
case custom((String) -> String)
/// 編碼key, 上面枚舉不太理解的話, 能夠看各個枚舉對應的方法實現就能夠理解了
func encode(_ key: String) -> String {
switch self {
case .useDefaultKeys: return key//不處理
case .convertToSnakeCase: return convertToSnakeCase(key)
case .convertToKebabCase: return convertToKebabCase(key)
case .capitalized: return String(key.prefix(1).uppercased() + key.dropFirst())//首字母大寫而後加上剩餘字符串
case .uppercased: return key.uppercased()//所有大寫
case .lowercased: return key.lowercased()//所有小寫
case let .custom(encoding): return encoding(key)
}
}
//蛇形
private func convertToSnakeCase(_ key: String) -> String {
convert(key, usingSeparator: "_")
}
//串形
private func convertToKebabCase(_ key: String) -> String {
convert(key, usingSeparator: "-")
}
//把駝峯寫法的key轉爲使用separator分割的新key
// 算法: 從開始查找字符串大小寫部分, 假定字符串開始爲小寫, 碰到第一個大寫字母:
// 1.若只有一個大寫字母, 就認爲該大寫字母到下一個大寫字母前的字符串爲一個單詞
// 2.不然, 認爲該大寫字母到小寫字母前的倒數第二個字母爲一個單詞
// 反覆查找, 把字符串分爲多個子字符串, 所有轉爲小寫, 使用separator鏈接
// 例如:myProperty -> my_property, myURLProperty -> my_url_property
// 注意: 由於會便利ztring, 因此會有明顯的性能影響
private func convert(_ key: String, usingSeparator separator: String) -> String {
guard !key.isEmpty else { return key }
// 存放分割字符串的range
var words: [Range<String.Index>] = []
// 開始查找的index
var wordStart = key.startIndex
// 查找字符串的range
var searchRange = key.index(after: wordStart)..<key.endIndex
// 開始遍歷字符串查找
while let upperCaseRange = key.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
// 大寫字母前的range(第一個小寫字符串)
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
// 加入words
words.append(untilUpperCase)
// 從大寫字符串後找小寫字符串的range
searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
guard let lowerCaseRange = key.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
// There are no more lower case letters. Just end here.
// 若沒有小寫字符串了, 跳出循環
wordStart = searchRange.lowerBound
break
}
// 若是大寫字符串長度大於1, 就把大寫字符串認爲是一個word
let nextCharacterAfterCapital = key.index(after: upperCaseRange.lowerBound)//大寫字符串range的startIndex的後一位
if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
// 是否與小寫字符串的startIndex相等, 相等表示大寫字符串只有一個字符, 就把這個字符跟後面的小寫字符串一塊兒當作一個word
wordStart = upperCaseRange.lowerBound
} else {
// 不然把大寫字符串開始到小寫字符串的startIndex的前一位當作一個word
// 例如: URLProperty搜索出來大寫字符串爲URLP, 就把URL當作一個word, Property當作後一個word
let beforeLowerIndex = key.index(before: lowerCaseRange.lowerBound)
// 加入words
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
// 設置wordStart, 下次查找到字符串後取word用
wordStart = beforeLowerIndex
}
// 下次搜索從小寫字符串range的尾部直到搜索range的尾部
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
}
// 循環完成, 加入結尾range
words.append(wordStart..<searchRange.upperBound)
// 所有變成小寫, 使用separator鏈接
let result = words.map { range in
key[range].lowercased()
}.joined(separator: separator)
return result
}
}
複製代碼
空格的編碼有兩個選擇:閉包
public enum SpaceEncoding {
/// 轉爲%20
case percentEscaped
/// 轉爲+
case plusReplaced
func encode(_ string: String) -> String {
switch self {
case .percentEscaped: return string.replacingOccurrences(of: " ", with: "%20")
case .plusReplaced: return string.replacingOccurrences(of: " ", with: "+")
}
}
}
複製代碼
定義了Error枚舉來在編碼出錯時拋出異常,只有一個錯誤:invalidRootObjecturl query string編碼要求參數根必須是key-value類型的app
/// URL編碼錯誤
public enum Error: Swift.Error {
/// root節點必須是key-value數據
case invalidRootObject(String)
var localizedDescription: String {
switch self {
case let .invalidRootObject(object):
return "URLEncodedFormEncoder requires keyed root object. Received \(object) instead."
}
}
}
複製代碼
初始化時使用了8個參數控制編碼行爲,其中alphabetizeKeyValuePairs參數能夠使得編碼出來的key-value數據使用key排序,不過該api僅限iOS13以上使用。工具
/// 編碼後的鍵值對是否根據key排序, 默認爲true, 相同的params編碼出來的字典數據是相同的, 若是設置了false, 由於字典的無序性, 會致使相同params編碼出來的字典順序不一樣
public let alphabetizeKeyValuePairs: Bool
/// The `ArrayEncoding` to use.
public let arrayEncoding: ArrayEncoding
/// The `BoolEncoding` to use.
public let boolEncoding: BoolEncoding
/// THe `DataEncoding` to use.
public let dataEncoding: DataEncoding
/// The `DateEncoding` to use.
public let dateEncoding: DateEncoding
/// The `KeyEncoding` to use.
public let keyEncoding: KeyEncoding
/// The `SpaceEncoding` to use.
public let spaceEncoding: SpaceEncoding
/// The `CharacterSet` of allowed (non-escaped) characters.
public var allowedCharacters: CharacterSet
// 初始化, 所有屬性都有默認值
public init(alphabetizeKeyValuePairs: Bool = true, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric, dataEncoding: DataEncoding = .base64, dateEncoding: DateEncoding = .deferredToDate, keyEncoding: KeyEncoding = .useDefaultKeys, spaceEncoding: SpaceEncoding = .percentEscaped, allowedCharacters: CharacterSet = .afURLQueryAllowed) {
self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs
self.arrayEncoding = arrayEncoding
self.boolEncoding = boolEncoding
self.dataEncoding = dataEncoding
self.dateEncoding = dateEncoding
self.keyEncoding = keyEncoding
self.spaceEncoding = spaceEncoding
self.allowedCharacters = allowedCharacters
}
複製代碼
內部編碼方法把參數編碼爲URLEncodedFormComponent類型 兩個公開編碼方法會先調用內部編碼方法,再使用URLEncodedFormSerializer解析爲String或者Data類型返回。
/// 核心編碼方法, 把value編碼成自定義的URLEncodedFormComponent數據(默認會編碼成字典類型),
/// 另外兩個編碼方法都會先調用該方法, 在對數據進行處理
func encode(_ value: Encodable) throws -> URLEncodedFormComponent {
// 表單數據的格式, 默認爲字典類型
let context = URLEncodedFormContext(.object([]))
// 編碼器
let encoder = _URLEncodedFormEncoder(context: context,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
try value.encode(to: encoder)
return context.component
}
public func encode(_ value: Encodable) throws -> String {
// 先編碼成URLEncodedFormComponent
let component: URLEncodedFormComponent = try encode(value)
// 轉成字典類型數據這裏object的類型是一個包含key,value元組
// 不是直接的字典, 由於字典無序, 使用元組數組能夠保證keyvalue的順序
guard case let .object(object) = component else {
throw Error.invalidRootObject("\(component)")
}
// 序列化
let serializer = URLEncodedFormSerializer(alphabetizeKeyValuePairs: alphabetizeKeyValuePairs,
arrayEncoding: arrayEncoding,
keyEncoding: keyEncoding,
spaceEncoding: spaceEncoding,
allowedCharacters: allowedCharacters)
// 序列化成query string
let query = serializer.serialize(object)
return query
}
public func encode(_ value: Encodable) throws -> Data {
// 先轉成query string
let string: String = try encode(value)
// 再utf8編碼
return Data(string.utf8)
}
複製代碼
//MARK: URLEncodedFormComponent,保存編碼的數據
enum URLEncodedFormComponent {
//對應key-value數據對
typealias Object = [(key: String, value: URLEncodedFormComponent)]
case string(String)//字符串
case array([URLEncodedFormComponent])//數組
case object(Object)//有序字典
/// 快速獲取數組數據, 字符串與字典會返回nil
var array: [URLEncodedFormComponent]? {
switch self {
case let .array(array): return array
default: return nil
}
}
/// 快速獲取字典數據, 字符串與數組會返回nil
var object: Object? {
switch self {
case let .object(object): return object
default: return nil
}
}
/// 把值根據keypaths設置進來
/// 參數value是要設置的值, path是keypath數組, 有三種狀況:
/// 1.path爲空數組, 表示直接把value設置成自身 例: data.set(to: "hello", at: [])
/// 2.path爲int類型, 表示須要使用數組保存 例: data.set(to: "hello", at: ["1"])
/// 3.path爲string類型, 表示須要使用字典保存 例: data.set(to: "hello", at: ["path", "to", "value"])
/// 保存方式爲從第一個path開始遞歸到最後一個path, 根據path從當前自身節點開始查找建立一個個節點, 把值在最後一個節點, 而後倒騰回來根據path類型設置一個個層級的數據類型, 最後完成整個數據樹
public mutating func set(to value: URLEncodedFormComponent, at path: [CodingKey]) {
set(&self, to: value, at: path)
}
/// 遞歸設置key-value
/// 參數context: 遞歸的當前節點, value: 須要保存的值, path: 保存的keypaths
/// 最初調用時, context時self節點, 隨着每一次遞歸, 會根據path的順序一層層往下傳, context也會一層層節點的往下查找建立, 最後完成整個數據樹
private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [CodingKey]) {
guard path.count >= 1 else {
//若是path爲空數組, 直接把value設置給當前節點, return
context = value
return
}
//第一個path
let end = path[0]
//子節點, 須要根據path去判斷子節點的類型
var child: URLEncodedFormComponent
switch path.count {
case 1:
//path只有一個, 就保存child就行
child = value
case 2...:
//paht有多個, 須要遞歸
if let index = end.intValue {
//第一個path是int, 須要用數組保存
//獲取當前節點的array類型
let array = context.array ?? []
if array.count > index {
// array數據大於index表示更新, 取出須要更新的節點做爲子節點
child = array[index]
} else {
//不然是新增, 建立子節點
child = .array([])
}
//開始遞歸
set(&child, to: value, at: Array(path[1...]))
} else {
//用字典保存
//根據第一個path, 找到子節點, 找獲得就是更新數據, 找不到就是新增須要建立子節點
child = context.object?.first { $0.key == end.stringValue }?.value ?? .object(.init())
//遞歸
set(&child, to: value, at: Array(path[1...]))
}
default: fatalError("Unreachable")
}
//遞歸回來, 這時候子節點自身已經處理完畢, 須要把子節點(child)插入到當前節點(context)中
if let index = end.intValue {
//第一個path是數組
if var array = context.array {
//若是當前節點爲數組節點, 直接把child插入或者更新到數組中
if array.count > index {
//更新
array[index] = child
} else {
//插入
array.append(child)
}
//更新當前節點
context = .array(array)
} else {
//不然, 直接把當前節點設置爲數組節點
context = .array([child])
}
} else {
//第一個path是字典
if var object = context.object {
//若是當前節點爲字典節點, 把child插入或更新進去
if let index = object.firstIndex(where: { $0.key == end.stringValue }) {
//更新
object[index] = (key: end.stringValue, value: child)
} else {
//插入
object.append((key: end.stringValue, value: child))
}
//更新當前節點
context = .object(object)
} else {
//不然, 把當前節點設置爲字典節點
context = .object([(key: end.stringValue, value: child)])
}
}
}
}
複製代碼
只是持有着一個URLEncodedFormComponent枚舉屬性,用來編碼時上下傳遞,逐個往裏面塞入新的編碼數據。最終編碼完成返回的結果就是持有的屬性
//MARK: URLEncodedFormContext編碼中遞歸傳遞的上下文, 持有保存的數據對象
final class URLEncodedFormContext {
var component: URLEncodedFormComponent
init(_ component: URLEncodedFormComponent) {
self.component = component
}
}
複製代碼
能夠保存字典的key(String類型),能夠保存數組的index(Int類型)
// 把int或者string轉換成CodingKey的容器
struct AnyCodingKey: CodingKey, Hashable {
let stringValue: String
let intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
intValue = nil
}
init?(intValue: Int) {
stringValue = "\(intValue)"
self.intValue = intValue
}
init<Key>(_ base: Key) where Key: CodingKey {
if let intValue = base.intValue {
self.init(intValue: intValue)!
} else {
self.init(stringValue: base.stringValue)!
}
}
}
複製代碼
// 用來把數據編碼成URLEncodedFormComponent表單數據的編碼器
final class _URLEncodedFormEncoder {
// Encoder協議屬性, 用來編碼key-value數據
var codingPath: [CodingKey]
// userinfo, 該編碼器不支持userinfo, 因此直接返回空數據
var userInfo: [CodingUserInfoKey: Any] { [:] }
// 編碼時遞歸傳遞的上下文, 包裹着URLEncodedFormComponent最終數據
let context: URLEncodedFormContext
//三種特殊類型的編碼方式
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
init(context: URLEncodedFormContext, codingPath: [CodingKey] = [], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) {
self.context = context
self.codingPath = codingPath
self.boolEncoding = boolEncoding
self.dataEncoding = dataEncoding
self.dateEncoding = dateEncoding
}
}
複製代碼
主要是須要返回三種數據編碼後的儲存容器。三種容器均使用內部類的形式寫在下面的擴展中
//MARK: 擴展_URLEncodedFormEncoder實現Encoder協議, 用來編碼數據
extension _URLEncodedFormEncoder: Encoder {
// 保存key-value數據的容器
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey {
//返回_URLEncodedFormEncoder.KeyedContainer, 數據會存在context中
let container = _URLEncodedFormEncoder.KeyedContainer<Key>(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return KeyedEncodingContainer(container)
}
//保存數組數據的容器
func unkeyedContainer() -> UnkeyedEncodingContainer {
_URLEncodedFormEncoder.UnkeyedContainer(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
//保存單個值的容器
func singleValueContainer() -> SingleValueEncodingContainer {
_URLEncodedFormEncoder.SingleValueContainer(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
}
複製代碼
主要做用是用來編碼字典數據,自己類聲明中只是保存一些屬性與定義了一個追加keypath的方法:
extension _URLEncodedFormEncoder {
final class KeyedContainer<Key> where Key: CodingKey {
var codingPath: [CodingKey]
private let context: URLEncodedFormContext
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
init(context: URLEncodedFormContext, codingPath: [CodingKey], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) {
self.context = context
self.codingPath = codingPath
self.boolEncoding = boolEncoding
self.dataEncoding = dataEncoding
self.dateEncoding = dateEncoding
}
//嵌套追加key, 在現有keypaths上繼續追加
private func nestedCodingPath(for key: CodingKey) -> [CodingKey] {
codingPath + [key]
}
}
}
複製代碼
用來編碼數據,主要是追加keypath而後根據value的類型把編碼任務派發下去,派發出去的子類型也是三種:
extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
// 不支持編碼nil數據, 因此直接拋出異常
func encodeNil(forKey key: Key) throws {
let context = EncodingError.Context(codingPath: codingPath,
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
throw EncodingError.invalidValue("\(key): nil", context)
}
// 編碼單個數據
func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
// 建立一個嵌套值編碼器來編碼
var container = nestedSingleValueEncoder(for: key)
try container.encode(value)
}
//建立嵌套單個數據編碼容器
func nestedSingleValueEncoder(for key: Key) -> SingleValueEncodingContainer {
let container = _URLEncodedFormEncoder.SingleValueContainer(context: context,
codingPath: nestedCodingPath(for: key),
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return container
}
//嵌套數組編碼容器
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
let container = _URLEncodedFormEncoder.UnkeyedContainer(context: context,
codingPath: nestedCodingPath(for: key),
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return container
}
//嵌套key-value編碼容器
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
let container = _URLEncodedFormEncoder.KeyedContainer<NestedKey>(context: context,
codingPath: nestedCodingPath(for: key),
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return KeyedEncodingContainer(container)
}
//父編碼器
func superEncoder() -> Encoder {
_URLEncodedFormEncoder(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
//父編碼器
func superEncoder(forKey key: Key) -> Encoder {
_URLEncodedFormEncoder(context: context,
codingPath: nestedCodingPath(for: key),
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
}
複製代碼
類的聲明中也只是定義了一些屬性,與上面KeyedEncodingContainer不一樣的是持有一個count屬性用來記錄數據個數做爲index keypath使用
extension _URLEncodedFormEncoder {
final class UnkeyedContainer {
var codingPath: [CodingKey]
var count = 0//記錄數組index, 每新增一個值就會+1
var nestedCodingPath: [CodingKey] {
codingPath + [AnyCodingKey(intValue: count)!]
}
private let context: URLEncodedFormContext
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
init(context: URLEncodedFormContext, codingPath: [CodingKey], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) {
self.context = context
self.codingPath = codingPath
self.boolEncoding = boolEncoding
self.dataEncoding = dataEncoding
self.dateEncoding = dateEncoding
}
}
}
複製代碼
用來編碼數據,相似KeyedContainer,由於value是容器,所以編碼操做也只是追加keypath,而後把編碼任務派發下去,派發出去的也是三種:
extension _URLEncodedFormEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
//也是不支持編碼nil
func encodeNil() throws {
let context = EncodingError.Context(codingPath: codingPath,
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
throw EncodingError.invalidValue("nil", context)
}
//編碼單個值
func encode<T>(_ value: T) throws where T: Encodable {
//使用單數據編碼容器編碼
var container = nestedSingleValueContainer()
try container.encode(value)
}
//單個數據編碼容器
func nestedSingleValueContainer() -> SingleValueEncodingContainer {
//編碼完成,個數+1
defer { count += 1 }
return _URLEncodedFormEncoder.SingleValueContainer(context: context,
codingPath: nestedCodingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
//key-value編碼容器
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
defer { count += 1 }
let container = _URLEncodedFormEncoder.KeyedContainer<NestedKey>(context: context,
codingPath: nestedCodingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return KeyedEncodingContainer(container)
}
//數組編碼容器
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
defer { count += 1 }
return _URLEncodedFormEncoder.UnkeyedContainer(context: context,
codingPath: nestedCodingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
//父編碼器
func superEncoder() -> Encoder {
defer { count += 1 }
return _URLEncodedFormEncoder(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
}
複製代碼
extension _URLEncodedFormEncoder {
final class SingleValueContainer {
var codingPath: [CodingKey]
private var canEncodeNewValue = true
private let context: URLEncodedFormContext
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
init(context: URLEncodedFormContext, codingPath: [CodingKey], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) {
self.context = context
self.codingPath = codingPath
self.boolEncoding = boolEncoding
self.dataEncoding = dataEncoding
self.dateEncoding = dateEncoding
}
//檢測是否能編碼新數據(編碼一個值後canEncodeNewValue就會被設置爲false, 就不容許在編碼新值了)
private func checkCanEncode(value: Any?) throws {
guard canEncodeNewValue else {
let context = EncodingError.Context(codingPath: codingPath,
debugDescription: "Attempt to encode value through single value container when previously value already encoded.")
throw EncodingError.invalidValue(value as Any, context)
}
}
}
}
複製代碼
extension _URLEncodedFormEncoder.SingleValueContainer: SingleValueEncodingContainer {
//不支持編碼nil
func encodeNil() throws {
try checkCanEncode(value: nil)
defer { canEncodeNewValue = false }
let context = EncodingError.Context(codingPath: codingPath,
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
throw EncodingError.invalidValue("nil", context)
}
//MARK: 一大堆編碼值得方法, 最終都是調用一個私有方法來編碼
func encode(_ value: Bool) throws {
try encode(value, as: String(boolEncoding.encode(value)))
}
func encode(_ value: String) throws {
try encode(value, as: value)
}
func encode(_ value: Double) throws {
try encode(value, as: String(value))
}
func encode(_ value: Float) throws {
try encode(value, as: String(value))
}
func encode(_ value: Int) throws {
try encode(value, as: String(value))
}
func encode(_ value: Int8) throws {
try encode(value, as: String(value))
}
func encode(_ value: Int16) throws {
try encode(value, as: String(value))
}
func encode(_ value: Int32) throws {
try encode(value, as: String(value))
}
func encode(_ value: Int64) throws {
try encode(value, as: String(value))
}
func encode(_ value: UInt) throws {
try encode(value, as: String(value))
}
func encode(_ value: UInt8) throws {
try encode(value, as: String(value))
}
func encode(_ value: UInt16) throws {
try encode(value, as: String(value))
}
func encode(_ value: UInt32) throws {
try encode(value, as: String(value))
}
func encode(_ value: UInt64) throws {
try encode(value, as: String(value))
}
//私有的泛型編碼數據方法
private func encode<T>(_ value: T, as string: String) throws where T: Encodable {
//先檢查是否能編碼新值
try checkCanEncode(value: value)
//做用於結束後(編碼完成),把開關設置爲不容許編碼新值
defer { canEncodeNewValue = false }
//把值使用string存進context
context.component.set(to: .string(string), at: codingPath)
}
// 編碼非標準類型的泛型數據
// 除了上面的標準類型外, 其餘數據類型的編碼會調用該泛型方法
// 這裏對Date, Data, Decimal先進行了判斷處理, 會先試圖以原數據類型進行編碼, 若是沒有規定編碼方法, 就使用_URLEncodedFormEncoder對value再次進行編碼, 系統會使用value的底層數據類型進行再次編碼(Date會使用Double, Data會使用[UInt8]數組)
func encode<T>(_ value: T) throws where T: Encodable {
//
switch value {
case let date as Date:
//Date判斷下是否使用Date默認類型進行延遲編碼
guard let string = try dateEncoding.encode(date) else {
//若是是用默認類型進行延遲編碼, 就使用_URLEncodedFormEncoder再次編碼
try attemptToEncode(value)
return
}
//不然使用string編碼
try encode(value, as: string)
case let data as Data:
//Data的處理相似上面Date處理
guard let string = try dataEncoding.encode(data) else {
try attemptToEncode(value)
return
}
try encode(value, as: string)
case let decimal as Decimal:
// Decimal默認的編碼數據類型是對象, 因此這裏攔截下, 轉成String格式
try encode(value, as: String(describing: decimal))
default:
// 其餘非標準類型所有使用默認類型編碼
try attemptToEncode(value)
}
}
// 編碼時二次調用, 使用value的原類型的默認編碼格式來編碼處理
private func attemptToEncode<T>(_ value: T) throws where T: Encodable {
try checkCanEncode(value: value)
defer { canEncodeNewValue = false }
let encoder = _URLEncodedFormEncoder(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
try value.encode(to: encoder)
}
}
複製代碼
編碼完成後保存數據的是URLEncodedFormComponent枚舉類型,這是個數據樹,須要把數據樹轉換成String或者Data返回給上層。所以定義了該解析器,用來把結果序列化成String
final class URLEncodedFormSerializer {
//是否把key-value數據排序
private let alphabetizeKeyValuePairs: Bool
private let arrayEncoding: URLEncodedFormEncoder.ArrayEncoding
private let keyEncoding: URLEncodedFormEncoder.KeyEncoding
private let spaceEncoding: URLEncodedFormEncoder.SpaceEncoding
private let allowedCharacters: CharacterSet
init(alphabetizeKeyValuePairs: Bool, arrayEncoding: URLEncodedFormEncoder.ArrayEncoding, keyEncoding: URLEncodedFormEncoder.KeyEncoding, spaceEncoding: URLEncodedFormEncoder.SpaceEncoding, allowedCharacters: CharacterSet) {
self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs
self.arrayEncoding = arrayEncoding
self.keyEncoding = keyEncoding
self.spaceEncoding = spaceEncoding
self.allowedCharacters = allowedCharacters
}
//MARK: 四個解析方法, 嵌套調用
//解析根字典對象
func serialize(_ object: URLEncodedFormComponent.Object) -> String {
var output: [String] = []
for (key, component) in object {
//便利字典, 把每對數據解析爲string
let value = serialize(component, forKey: key)
output.append(value)
}
//排序
output = alphabetizeKeyValuePairs ? output.sorted() : output
//使用&拼接string返回
return output.joinedWithAmpersands()
}
//解析字典中的對象, 格式爲: key=value
func serialize(_ component: URLEncodedFormComponent, forKey key: String) -> String {
switch component {
//string直接對key進行編碼,而後拼接成字符串
case let .string(string): return "\(escape(keyEncoding.encode(key)))=\(escape(string))"
//數組字典調下面兩個解析方法
case let .array(array): return serialize(array, forKey: key)
case let .object(object): return serialize(object, forKey: key)
}
}
//字典中的字典對象, 格式爲: key[subKey]=value
func serialize(_ object: URLEncodedFormComponent.Object, forKey key: String) -> String {
var segments: [String] = object.map { subKey, value in
let keyPath = "[\(subKey)]"
return serialize(value, forKey: key + keyPath)
}
segments = alphabetizeKeyValuePairs ? segments.sorted() : segments
return segments.joinedWithAmpersands()
}
//字典中的數組對象, 格式爲: key[]=value或者key=value
func serialize(_ array: [URLEncodedFormComponent], forKey key: String) -> String {
var segments: [String] = array.map { component in
let keyPath = arrayEncoding.encode(key)
return serialize(component, forKey: keyPath)
}
segments = alphabetizeKeyValuePairs ? segments.sorted() : segments
return segments.joinedWithAmpersands()
}
//url轉義
func escape(_ query: String) -> String {
var allowedCharactersWithSpace = allowedCharacters
allowedCharactersWithSpace.insert(charactersIn: " ")
let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: allowedCharactersWithSpace) ?? query
let spaceEncodedQuery = spaceEncoding.encode(escapedQuery)
return spaceEncodedQuery
}
}
複製代碼
//使用&鏈接
extension Array where Element == String {
func joinedWithAmpersands() -> String {
joined(separator: "&")
}
}
// 須要轉義的字符
extension CharacterSet {
/// Creates a CharacterSet from RFC 3986 allowed characters.
///
/// RFC 3986 states that the following characters are "reserved" characters.
///
/// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
///
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
/// should be percent-escaped in the query string.
public static let afURLQueryAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters)
}()
}
複製代碼