Forward navigation:
Alamofire source learning directory collection
Brief introduction:
In the previous article Alamofire source learning (nine): ParameterEncoder is used to add any Encodable parameter codes to URLRequest. In the default implementation URLEncodedFormParameterEncoder class, is the custom to achieve for the coding parameters URLEncodedFormEncoder encoder, used to Encodable protocol parameters of the code for the url query string, Parameter types can be basic Data types (Int, Double, and so on) or other advanced types (Data, Date, Decimal, and so on) or custom types that are implemented for the Encodable protocol.
URLEncodedFormEncoder- pretends to be an encoder, but is really just a container class used to define types
The URLEncodedFormEncoder class itself does not implement the encoding method, but only defines the behavior of N multiple encoding. The real encoding is the inner class _URLEncodedFormEncoder, where all the encoding is done. The encoded data is stored in the URLEncodedFormComponent. Passed to the upper URLEncodedFormParameterEncoder, use URLEncodedFormSerializer encoded data serialization for the url query string.
Encoding format for partial data types
URLEncodedFormEncoder defines the encoding format of 4 data types, which can be selected freely:
1. Array encoding mode:
/// array encoding
public enum ArrayEncoding {
case brackets
case noBrackets
func encode(_ key: String) -> String {
switch self {
case .brackets: return "\(key)[]"
case .noBrackets: return key
}
}
}
Copy the code
2.Bool Encoding mode:
/// Bool Encoding
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"}}}Copy the code
3.Data encoding mode:
The delay encoding mode of Data is as follows: In custom coding, if Data is encoded in the deferredToData type, a subencoder will be created to encode Data using the default encoding format of Data (UInt8 array).
/// Data encoding mode
public enum DataEncoding {
/// delay encoding to Data
case deferredToData
/// base64 string encoding
case base64
/// Use closures to encode custom formatted strings
case custom((Data) throws -> String)
/ / / the data coding
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)
}
}
}
Copy the code
4.Date encoding mode
The delay encoding of Date is similar to that of Data, but the default encoding format is 1970.1.1 seconds from Double. ms
public enum DateEncoding {
/// The Formatter used to convert Data to ISO8601 strings
private static let iso8601Formatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
return formatter
}()
/// Delay is encoded as Date
case deferredToDate
/// encode to a string of seconds starting from 1910.1.1
case secondsSince1970
/// encode as a string of milliseconds starting from 1910.1.1
case millisecondsSince1970
/// Is encoded as an ISO8601 standard character string
case iso8601
// use custom formatter encoding
case formatted(DateFormatter)
// use closures to customize the code 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)
}
}
}
Copy the code
Encoding format of Key
Key encoding from systematic JSONEncoder. KeyEncodingStrategy with XMLEncoder. KeyEncodingStrategy common derived encoding is mainly aimed at the Key string forms are defined:
public enum KeyEncoding {
/// Default format, no key encoding
case useDefaultKeys
OneTwoThree -> one_two_three
case convertToSnakeCase
OntTwoThree -> one-two-three
case convertToKebabCase
/// initials: oneTwoThree -> oneTwoThree
case capitalized
OneTwoThree -> oneTwoThree
case uppercased
OneTwoThree -> oneTwoThree
case lowercased
// use closures to customize coding rules
case custom((String) - >String)
/// select key (); // select key ()
func encode(_ key: String) -> String {
switch self {
case .useDefaultKeys: return key/ / not processing
case .convertToSnakeCase: return convertToSnakeCase(key)
case .convertToKebabCase: return convertToKebabCase(key)
case .capitalized: return String(key.prefix(1).uppercased() + key.dropFirst())// Capitalize the first letter and add the rest of the string
case .uppercased: return key.uppercased()// All caps
case .lowercased: return key.lowercased()// All in lowercase
case let .custom(encoding): return encoding(key)
}
}
/ / snake
private func convertToSnakeCase(_ key: String) -> String {
convert(key, usingSeparator: "_")}/ / string form
private func convertToKebabCase(_ key: String) -> String {
convert(key, usingSeparator: "-")}// Change the hump key to the new key using separator
// Algorithm: searches the upper and lower case parts of the string from the beginning, assuming that the string starts in lower case and encounters the first uppercase character:
// 1. If there is only one uppercase letter, the string before the next uppercase letter is considered as a word
// 2. Otherwise, the last but one letter from the uppercase letter to the lowercase letter is considered a word
// Search repeatedly, divide the string into multiple substrings, convert all to lowercase, and use separator to join
For example, myProperty -> my_property, myURLProperty -> my_url_property
// Note: Since it facilitates Ztring, there is a significant performance impact
private func convert(_ key: String.usingSeparator separator: String) -> String {
guard !key.isEmpty else { return key }
// Range of split strings
var words: [Range<String.Index>] = []
// Start the search index
var wordStart = key.startIndex
// Find the range of strings
var searchRange = key.index(after: wordStart)..<key.endIndex
// Start iterating through the string search
while let upperCaseRange = key.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
// Range before uppercase (first lowercase string)
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
/ / add words
words.append(untilUpperCase)
// Find the range from the uppercase string to the lowercase string
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.
// If there are no lower case strings, break out of the loop
wordStart = searchRange.lowerBound
break
}
// If the uppercase string is longer than 1, the uppercase string is considered a Word
let nextCharacterAfterCapital = key.index(after: upperCaseRange.lowerBound)// The last bit of startIndex in the uppercase string range
if lowerCaseRange.lowerBound = = nextCharacterAfterCapital {
Equal to startIndex of lowercase string. Equal means uppercase string has only one character. Treat that character as a word along with the lowercase string that follows
wordStart = upperCaseRange.lowerBound
} else {
// Otherwise treat the beginning of the uppercase string to the startIndex of the lowercase string as a word
// For example, if the URLProperty search returns an uppercase string URLP, consider the URL as a word and the Property as the latter word
let beforeLowerIndex = key.index(before: lowerCaseRange.lowerBound)
/ / add words
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
// Set wordStart to use word when the string is found next time
wordStart = beforeLowerIndex
}
// Write the string from the end of range to the end of range for the next search
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
}
// When the loop is complete, add the end range
words.append(wordStart..<searchRange.upperBound)
// Make all lowercase using separator
let result = words.map { range in
key[range].lowercased()
}.joined(separator: separator)
return result
}
}
Copy the code
The encoding format of the space
The encoding of Spaces has two options:
public enum SpaceEncoding {
/ / / to % 20
case percentEscaped
/ / / to +
case plusReplaced
func encode(_ string: String) -> String {
switch self {
case .percentEscaped: return string.replacingOccurrences(of: "", with: "% 20")
case .plusReplaced: return string.replacingOccurrences(of: "", with: "+")}}}Copy the code
Error definition when encoding error
The Error enumeration is defined to throw an exception if the encoding fails. There is only one Error: invalidRootObjecturl Query String The encoding requires that the argument root must be of type key-value
/// URL encoding error
public enum Error: Swift.Error {
/// The root node must contain key-value data
case invalidRootObject(String)
var localizedDescription: String {
switch self {
case let .invalidRootObject(object):
return "URLEncodedFormEncoder requires keyed root object. Received \(object) instead."}}}Copy the code
Constant properties and initialization:
The alphabetizeKeyValuePairs parameter controls the encoding behavior, including alphabetizeKeyValuePairs parameter, which causes encoded key-value data to be sorted by key. This API is only available for iOS13 and older.
The default value is true. Dictionaries encoded by the same params will be the same. If false is set, dictionaries encoded by the same params will be in different order because of the unordered nature of the dictionaries
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
// All attributes have default values
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
}
Copy the code
One internal encoding method and two public encoding methods
The internal encoding method encodes parameters as URLEncodedFormComponent. The two public encoding methods call the internal encoding method and use URLEncodedFormSerializer to parse it as String or Data.
/// the core encoding method that encodes value as a custom URLEncodedFormComponent (dictionary by default),
/// The other two encoding methods call this method first, before processing the data
func encode(_ value: Encodable) throws -> URLEncodedFormComponent {
// Form data format, default is dictionary type
let context = URLEncodedFormContext(.object([]))
/ / encoder
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 {
// Encode URLEncodedFormComponent first
let component: URLEncodedFormComponent = try encode(value)
The type of object is a tuple containing key and value
// Not a direct dictionary, since dictionaries are out of order, using tuples ensures the order of keyvalues
guard case let .object(object) = component else {
throw Error.invalidRootObject("\(component)")}/ / the serialization
let serializer = URLEncodedFormSerializer(alphabetizeKeyValuePairs: alphabetizeKeyValuePairs,
arrayEncoding: arrayEncoding,
keyEncoding: keyEncoding,
spaceEncoding: spaceEncoding,
allowedCharacters: allowedCharacters)
// Serialize to query String
let query = serializer.serialize(object)
return query
}
public func encode(_ value: Encodable) throws -> Data {
// Change to query string
let string: String = try encode(value)
// utf8 encoding
return Data(string.utf8)
}
Copy the code
URLEncodedFormComponent enumeration — stores data recursively
- This enumeration is the encoded data type with three cases: string, array, and ordered dictionary
- Since the dictionary itself is unordered, an array of key and value tuples is defined to represent an ordered dictionary
- You can append URLEncodedFormComponent data to your current data from the path array, save it recursively by key, and eventually code a data tree.
- The setValue method is declared mutating because it is stuffing data into itself
//MARK: URLEncodedFormComponent, which holds encoded data
enum URLEncodedFormComponent {
// Corresponds to the key-value data pair
typealias Object = [(key: String, value: URLEncodedFormComponent)]
case string(String)/ / string
case array([URLEncodedFormComponent])/ / array
case object(Object)// Ordered dictionary
/// Get array data quickly, string and dictionary will return nil
var array: [URLEncodedFormComponent]? {
switch self {
case let .array(array): return array
default: return nil}}/// Get dictionary data quickly, strings and arrays return nil
var object: Object? {
switch self {
case let .object(object): return object
default: return nil}}/// Set the value to keypaths
/// The value argument is the value to be set, and the path is the keypath array.
Data. Set (to: "hello", at: []) /// 1.
Set (to: "hello", at: ["1"]) /// 2.
Set (to: "hello", at: ["path", "to", "value"]) /// 3.
/// Save the way from the first path recursively to the last path, according to the path from the current node to search to create a node, put the value in the last node, and then reverse back to set a hierarchical data type according to the path type, finally complete the entire data tree
public mutating func set(to value: URLEncodedFormComponent.at path: [CodingKey]) {
set(&self, to: value, at: path)
}
/// recursively set key-value
/// Parameters context: current recursive node, value: value to be saved, path: keypaths to be saved
/// When the context is called initially, the self node is passed down in the order of the path with each recursion, and the context is searched down in the order of the nodes to create the entire data tree
private func set(_ context: inout URLEncodedFormComponent.to value: URLEncodedFormComponent.at path: [CodingKey]) {
guard path.count > = 1 else {
// If path is an empty array, set value directly to the current node, return
context = value
return
}
// The first path
let end = path[0]
// The type of the child node needs to be determined according to the path
var child: URLEncodedFormComponent
switch path.count {
case 1:
// There is only one path, save child
child = value
case 2.:
// There are multiple paHT, need to recurse
if let index = end.intValue {
// The first path is an int and needs to be stored in an array
// Get the array type of the current node
let array = context.array ?? []
if array.count > index {
// Array data greater than index indicates an update, and the node to be updated is taken as a child node
child = array[index]
} else {
// Otherwise, create a child node
child = .array([])
}
// Start recursion
set(&child, to: value, at: Array(path[1.))}else {
// Save it with a dictionary
// Create a child node based on the first path
child = context.object?.first { $0.key = = end.stringValue }?.value ?? .object(.init())
/ / recursion
set(&child, to: value, at: Array(path[1.))}default: fatalError("Unreachable")}// The child has already been processed and needs to be inserted into the current context
if let index = end.intValue {
// The first path is an array
if var array = context.array {
// If the current node is an array node, insert or update child directly into the array
if array.count > index {
/ / update
array[index] = child
} else {
/ / insert
array.append(child)
}
// Update the current node
context = .array(array)
} else {
// Otherwise, set the current node as an array node
context = .array([child])
}
} else {
The first path is a dictionary
if var object = context.object {
// If the current node is a dictionary node, insert or update child
if let index = object.firstIndex(where: { $0.key = = end.stringValue }) {
/ / update
object[index] = (key: end.stringValue, value: child)
} else {
/ / insert
object.append((key: end.stringValue, value: child))
}
// Update the current node
context = .object(object)
} else {
// Otherwise, set the current node as a dictionary node
context = .object([(key: end.stringValue, value: child)])
}
}
}
}
Copy the code
URLEncodedFormContext- The context passed when encoding
It simply holds an URLEncodedFormComponent enumeration property that is passed up and down as it is encoded, filling it one by one with new encoded data. The result of the final encoding completion is the property held
//MARK: URLEncodedFormContext The recursively passed context in URLEncodedFormContext encoding that holds the saved data object
final class URLEncodedFormContext {
var component: URLEncodedFormComponent
init(_ component: URLEncodedFormComponent) {
self.component = component
}
}
Copy the code
AnyCodingKey- Implements CodeKey, the key type of the Hashable protocol
Can hold dictionary key (String), can hold array index (Int)
// Convert int or string to CodingKey container
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)!}}}Copy the code
_URLEncodedFormEncoder- the class that does the real work
- Encoder protocol is implemented, which is really used to encode data
- Holds an encapsulated URLEncodedFormContext property that is used to hold encoded data and be passed in recursively encoded data.
- You can set which data type is encoded
- Because Encoder protocol is implemented, codingPath array of type [CodingKey] is held
// Encoder used to encode data into URLEncodedFormComponent form data
final class _URLEncodedFormEncoder {
// Encoder protocol property, used to encode key-value data
var codingPath: [CodingKey]
// userinfo, this encoder does not support userinfo, so return null data directly
var userInfo: [CodingUserInfoKey: Any[:]] {}// The context in which the URLEncodedFormComponent is passed recursively while encoding, wrapping the URLEncodedFormComponent final data
let context: URLEncodedFormContext
// Three special types of encoding
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
}
}
Copy the code
An implementation of the Encoder protocol uses extended encapsulation
It is mainly the storage container that needs to return three kinds of data encoding. All three containers are written as inner classes in the extensions below
//MARK: the _URLEncodedFormEncoder extension implements the Encoder protocol, which is used to encode data
extension _URLEncodedFormEncoder: Encoder {
// A container to hold key-value data
func container<Key> (keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey {
/ / return _URLEncodedFormEncoder KeyedContainer, may data exist in the context
let container = _URLEncodedFormEncoder.KeyedContainer<Key>(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return KeyedEncodingContainer(container)
}
// A container for holding array data
func unkeyedContainer(a) -> UnkeyedEncodingContainer {
_URLEncodedFormEncoder.UnkeyedContainer(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
// A container to hold a single value
func singleValueContainer(a) -> SingleValueEncodingContainer {
_URLEncodedFormEncoder.SingleValueContainer(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
}
Copy the code
1.KeyedContainer internal class, used to store key-value data:
The main function is to encode dictionary data, and the class declaration itself only holds some attributes and defines an append keypath method:
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
}
// Add nested keys to the existing keypaths
private func nestedCodingPath(for key: CodingKey)- > [CodingKey] {
codingPath + [key]
}
}
}
Copy the code
The extension implementation KeyedEncodingContainerProtocol protocol:
It is used to encode data by adding keypath and assigning coding tasks according to the value type. The assigned sub-types are also three:
extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
// Encoding nil data is not supported, so throw an exception directly
func encodeNil(forKey key: Key) throws {
let context = EncodingError.Context(codingPath: codingPath,
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
throw EncodingError.invalidValue("\(key): nil", context)
}
// Encode single data
func encode<T> (_ value: T.forKey key: Key) throws where T: Encodable {
// Create a nested value encoder to encode
var container = nestedSingleValueEncoder(for: key)
try container.encode(value)
}
// Create a nested single data encoding container
func nestedSingleValueEncoder(for key: Key) -> SingleValueEncodingContainer {
let container = _URLEncodedFormEncoder.SingleValueContainer(context: context,
codingPath: nestedCodingPath(for: key),
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
return container
}
// A nested array encoding 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
}
// Nested key-value encoding containers
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)
}
// Parent encoder
func superEncoder(a) -> Encoder {
_URLEncodedFormEncoder(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
// Parent encoder
func superEncoder(forKey key: Key) -> Encoder {
_URLEncodedFormEncoder(context: context,
codingPath: nestedCodingPath(for: key),
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
}
Copy the code
2.UnkeyedContainer Internal class, which encodes array data
The KeyedEncodingContainer declaration also defines a few properties. Instead of KeyedEncodingContainer, it holds a count property that records the number of data to use as the index keypath
extension _URLEncodedFormEncoder {
final class UnkeyedContainer {
var codingPath: [CodingKey]
var count = 0// Record array index, each new value is +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
}
}
}
Copy the code
The extension implements the UnkeyedEncodingContainer protocol
It is used to encode data, similar to KeyedContainer. Since value is a container, the encoding operation is to append a keypath and then send the encoding task out.
extension _URLEncodedFormEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
// Encoding nil is also not supported
func encodeNil(a) throws {
let context = EncodingError.Context(codingPath: codingPath,
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
throw EncodingError.invalidValue("nil", context)
}
// Encode a single value
func encode<T> (_ value: T) throws where T: Encodable {
// Use single-data encoding container encoding
var container = nestedSingleValueContainer()
try container.encode(value)
}
// A single data encoding container
func nestedSingleValueContainer(a) -> SingleValueEncodingContainer {
// Complete encoding, number +1
defer { count + = 1 }
return _URLEncodedFormEncoder.SingleValueContainer(context: context,
codingPath: nestedCodingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
//key-value encoding container
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)
}
// Array encoding container
func nestedUnkeyedContainer(a) -> UnkeyedEncodingContainer {
defer { count + = 1 }
return _URLEncodedFormEncoder.UnkeyedContainer(context: context,
codingPath: nestedCodingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
// Parent encoder
func superEncoder(a) -> Encoder {
defer { count + = 1 }
return _URLEncodedFormEncoder(context: context,
codingPath: codingPath,
boolEncoding: boolEncoding,
dataEncoding: dataEncoding,
dateEncoding: dateEncoding)
}
}
Copy the code
3.SingleValueContainer inner class, the class that actually encodes values
- The first two container classes record keypath and send out the recursive code, and this class is the only one that encodes the specific value
- In addition to the basic attributes, there is a canEncodeNewValue attribute in the declaration, which indicates whether the value has been encoded. If the encoded key is duplicated, an error will be thrown
- The extension defines a number of methods for encoding data, including basic data types and special data types, using generics
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 is set to false after encoding a value, so encoding a new value is not allowed)
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)
}
}
}
}
Copy the code
The extension implementation SingleValueEncodingContainer agreement
- For basic types, the generic encode(value:as:) method is used to encode them into String format
- For other types, the generic encode method is used to encode according to the encoding format set by the user. In case of delayed encoding or no custom encoding format, a new encoder is used to encode into the default format
- Before each encoding, check whether the same keypath data has been encoded. If the same keypath data has been encoded, an exception will be thrown
extension _URLEncodedFormEncoder.SingleValueContainer: SingleValueEncodingContainer {
// Encoding nil is not supported
func encodeNil(a) 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: A bunch of code worthy methods end up calling a private method to code
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 generic encoding data methods
private func encode<T> (_ value: T.as string: String) throws where T: Encodable {
// Check to see if you can encode new values
try checkCanEncode(value: value)
// Set the switch to not allow new values to be encoded
defer { canEncodeNewValue = false }
// Store the value into context using string
context.component.set(to: .string(string), at: codingPath)
}
// Encode generic data of non-standard types
In addition to the standard type above, encodings of other data types call this generic method
// Date, Data, Decimal is tested first, and the original Data type is attempted first. If no encoding method is specified, value is encoded again using _URLEncodedFormEncoder. The system uses the underlying Data type of value to encode it again (Date uses Double and Data uses the [UInt8] array)
func encode<T> (_ value: T) throws where T: Encodable {
//
switch value {
case let date as Date:
//Date Determine whether to use the default Date type for delay encoding
guard let string = try dateEncoding.encode(date) else {
// If the default type is used for delay encoding, use _URLEncodedFormEncoder again
try attemptToEncode(value)
return
}
// Otherwise use string encoding
try encode(value, as: string)
case let data as Data:
//Data is handled in the same way as Date
guard let string = try dataEncoding.encode(data) else {
try attemptToEncode(value)
return
}
try encode(value, as: string)
case let decimal as Decimal:
// The default encoding data type for Decimal is object, so this is intercepted and converted to String format
try encode(value, as: String(describing: decimal))
default:
// All other non-standard types use the default type encoding
try attemptToEncode(value)
}
}
// Is called twice when encoding, using the default encoding format of the original type of 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)
}
}
Copy the code
URLEncodedFormSerializer– Serialized encoded result data
The URLEncodedFormComponent enumeration holds the Data after coding, which is a Data tree that needs to be converted to a String or Data to be returned to the upper layer. Therefore, the parser is defined to serialize the result to a String
- Five attributes are defined to control coding behavior
- Parsing starts with the root dictionary and recursively calls parsing methods to parse child dictionaries and arrays
- The final leaf node must be in String format
- Key-value sorting, URL escaping, and using & concatenation to query String are all done in this class
final class URLEncodedFormSerializer {
// Whether to sort key-value data
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: Four parsing methods, nested calls
// Parse the root dictionary object
func serialize(_ object: URLEncodedFormComponent.Object) -> String {
var output: [String] = []
for (key, component) in object {
// A convenience dictionary that parses each pair of data into a string
let value = serialize(component, forKey: key)
output.append(value)
}
/ / sorting
output = alphabetizeKeyValuePairs ? output.sorted() : output
// The & concatenation string is returned
return output.joinedWithAmpersands()
}
// Parse the object in the dictionary in the format key=value
func serialize(_ component: URLEncodedFormComponent.forKey key: String) -> String {
switch component {
// String encodes the key directly and concatenates it into a string
case let .string(string): return "\(escape(keyEncoding.encode(key)))=\(escape(string))"
// Array dictionary calls the following two parsing methods
case let .array(array): return serialize(array, forKey: key)
case let .object(object): return serialize(object, forKey: key)
}
}
// Dictionary object in the dictionary. The format is 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()
}
// An array object in the dictionary of the format key[]=value or 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 escaped
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
}
}
Copy the code
Two tool extensions
- The extension Array is used to concatenate the final Query String with &
- Extend CharacterSet to define characters that do not require URL escape
// Use & connection
extension Array where Element= =String {
func joinedWithAmpersands(a) -> String {
joined(separator: "&")}}// The character to escape
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)
}()
}
Copy the code