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