在上一篇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)
}()
}
复制代码