Forward navigation:

Alamofire source learning directory collection

Introduction to the

These are the parameters that are passed in to encode the parameters when you create the request

The same

  • Both are used when creating a Request in a Session
  • Are used to encode parameters into URLRequest
  • Can determine where the parameters are encoded (URL Query String, body form, bodyJSON)
  • UploadRequest doesn’t use either of these because it has no parameters

The difference between

  • The initialization parameters are different
  • ParameterEncoding can encode only dictionary data. ParameterEncoder is used to encode any data type that implements the Encodable protocol
  • ParameterEncoding is easy to implement because it is a dictionary. When ParameterEncoding body, you need to encode it as a query string and utF8 as data. ParameterEncoder uses a URLEncodedFormEncoder implemented by ParameterEncoder Alamofire to encode form Data. It can encode special Data such as Date and Data
  • ParameterEncoding is only used to create DataRequest and DownloadRequest. DataStreamRequest is not available. ParameterEncoder is used to initialize all three Request subclasses

ParameterEncoding

Parameters (alias [String: Any]) can only be used to encode dictionary Parameters. The protocol is very simple. There is only one method to encode Parameters into URLRequest and return a new URLRequest:

/// A dictionary of parameters to apply to a `URLRequest`.
public typealias Parameters = [String: Any]

/// A type used to define how a set of parameters are applied to a `URLRequest`.
public protocol ParameterEncoding {
    / / / using URLRequestConvertible URLRequest creation, and then put the dictionary coding into the URLRequest parameters, can throw an exception, throw an exception when returns AFError. ParameterEncodingFailed errors
    func encode(_ urlRequest: URLRequestConvertible.with parameters: Parameters?). throws -> URLRequest
}
Copy the code

Alamofire provides a default implementation for encoding URL Query String and JSON, respectively

1.URLEncoding default implementation, used to encode URL Query string

  • According to the location of the parameter encoding, there are two types: QueryString and form. The type is controlled by the Destination enumeration
  • If the form is coded, the content-Type of the request header is set to Application/X-www-form-urlencoded; charset=utf-8
  • Arrays and dictionaries are all encoded recursively
  • Because there is no uniform specification for how to encode collection parameters, array parameter encoding has two options, controlled by the ArrayEncoding enumeration: square brackets by default

    For example: key: [value1, value2] 1. Use a key followed by square brackets and an equal sign and value, for example: key[]= Value1&key []=value2 2

  • Dictionary parameter encoding uses key with square brackets subkey with equal sign and value

    key[subkey1]=value1&key[subkey2]=value2

  • Bool encoding can optionally use the value 0,1 or the strings true,false, controlled by the BoolEncoding enumeration, which defaults to values

Code comments:

public struct URLEncoding: ParameterEncoding {
    // MARK: Secondary data types

    // define whether the parameter is encoded in the URL query or in the body
    public enum Destination {
        // Method determines (get, head, delete are urlquery, others are body)
        case methodDependent
        /// url query
        case queryString
        /// body
        case httpBody
        /// Returns whether arguments are to be marshaled into the URL query
        func encodesParametersInURL(for method: HTTPMethod) -> Bool {
            switch self {
            case .methodDependent: return [.get, .head, .delete].contains(method)
            case .queryString: return true
            case .httpBody: return false}}}// decide how to encode Array
    public enum ArrayEncoding {
        /// key followed by parenthesis encoding
        case brackets
        /// key is not encoded with parentheses
        case noBrackets
        /// Encode the key
        func encode(key: String) -> String {
            switch self {
            case .brackets:
                return "\(key)[]"
            case .noBrackets:
                return key
            }
        }
    }

    /// Decide how to encode Bool
    public enum BoolEncoding {
        /// Digits: 1, 0
        case numeric
        /// string: true, false
        case literal
        /// encode the value
        func encode(value: Bool) -> String {
            switch self {
            case .numeric:
                return value ? "1" : "0"
            case .literal:
                return value ? "true" : "false"}}}// MARK: Quick initialization of three static computed properties

    /// The default encoding position is method, parenthesized array, and numeric bool
    public static var `default`: URLEncoding { URLEncoding()}/// url query encoding, array using parentheses, bool using numbers
    public static var queryString: URLEncoding { URLEncoding(destination: .queryString) }

    /// the form is encoded to the body, the array is bracketed, and the bool is numeric
    public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) }

    //MARK: Attributes and initializations
    /// parameter encoding position
    public let destination: Destination

    /// Array encoding format
    public let arrayEncoding: ArrayEncoding

    /// Bool Encoding format
    public let boolEncoding: BoolEncoding

    public init(destination: Destination = .methodDependent,
                arrayEncoding: ArrayEncoding = .brackets,
                boolEncoding: BoolEncoding = .numeric) {
        self.destination = destination
        self.arrayEncoding = arrayEncoding
        self.boolEncoding = boolEncoding
    }

    // MARK: Implements the protocol encoding method

    public func encode(_ urlRequest: URLRequestConvertible.with parameters: Parameters?). throws -> URLRequest {
        // Get the URLRequest
        var urlRequest = try urlRequest.asURLRequest()
        // Return with no arguments
        guard let parameters = parameters else { return urlRequest }
        // Get method first, then use method to determine where to encode parameters
        // If method is empty, you should throw an exception. ParameterEncoder has processing in it
        if let method = urlRequest.method, destination.encodesParametersInURL(for: method) {
            / / url query code
            guard let url = urlRequest.url else {
                // Throw an exception if the URL is empty
                throw AFError.parameterEncodingFailed(reason: .missingURL)
            }
            if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
                // Get the existing query string, add & if it exists, and concatenate the new query String
                let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
            }
        } else {
            / / body code
            if urlRequest.headers["Content-Type"] = = nil {
                / / set the content-type
                urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))}// Convert the query String to UTF8 encoding and throw it into the body
            urlRequest.httpBody = Data(query(parameters).utf8)
        }

        return urlRequest
    }
    
    /// encode key-value pairs. Value deals with dictionaries, arrays,nsnumber types bool,bool, and other values
    public func queryComponents(fromKey key: String.value: Any)- > [(String.String)] {
        var components: [(String.String)] = []
        switch value {
        case let dictionary as [String: Any] :// dictionary processing, traversing dictionary recursive calls
            for (nestedKey, value) in dictionary {
                components + = queryComponents(fromKey: "\(key)[\(nestedKey)]. "", value: value)
            }
        case let array as [Any] :for value in array {
                // Array handling, traversal recursive calls based on the type of array key encoding
                components + = queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
            }
        case let number as NSNumber:
            // nsNumber uses the objCType class to determine if it is bool
            if number.isBool {
                components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
            } else {
                components.append((escape(key), escape("\(number)")))}case let bool as Bool:
            //bool is processed according to the encoding type
            components.append((escape(key), escape(boolEncoding.encode(value: bool))))
        default:
            // Otherwise, convert to string
            components.append((escape(key), escape("\(value)")))}return components
    }

    /// the url is escaped and converted to a percentage sign format
    /// ignore :#[]@! * + $& '(),; =
    public func escape(_ string: String) -> String {
        string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
    }
    /// Convert the parameter dictionary to a query string
    private func query(_ parameters: [String: Any]) -> String {
        // Store key and value tuples
        var components: [(String.String)] = []
        
        for key in parameters.keys.sorted(by: <) {
            let value = parameters[key]! //Force unpack directly// Encode each key-value pair
            components + = queryComponents(fromKey: key, value: value)
        }
        // Concatenated as a query string
        return components.map { "\ [$0)=\ [The $1)" }.joined(separator: "&")}}Copy the code

JSONEncoding (JSONEncoding) : JSONEncoding (JSONEncoding) : JSONEncoding (JSONEncoding

Use JSONSerialization to encode the parameter dictionary as JSON, which must be encoded in the body and set the content-type to Application /json

Code comments:

public struct JSONEncoding: ParameterEncoding {
    // MARK: static computed variable for quick initialization

    // The default type, compressed JSON format
    public static var `default`: JSONEncoding { JSONEncoding()}// Standard JSON format
    public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }

    // MARK: Attributes and initializations
    
    / / save JSONSerialization WritingOptions
    public let options: JSONSerialization.WritingOptions

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

    // MARK: Implements the protocol encoding method

    public func encode(_ urlRequest: URLRequestConvertible.with parameters: Parameters?). throws -> URLRequest {
        / / get Request
        var urlRequest = try urlRequest.asURLRequest()

        guard let parameters = parameters else { return urlRequest }

        do {
            // Encode as data
            let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
            / / set the content-type
            if urlRequest.headers["Content-Type"] = = nil {
                urlRequest.headers.update(.contentType("application/json"))}/ / into the body
            urlRequest.httpBody = data
        } catch {
            // Error parsing json throws an error
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
        }

        return urlRequest
    }
    
    // Encode the json object into the body. The above encoding method can be directly removed from the body
    public func encode(_ urlRequest: URLRequestConvertible.withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
        var urlRequest = try urlRequest.asURLRequest()

        guard let jsonObject = jsonObject else { return urlRequest }

        do {
            let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)

            if urlRequest.headers["Content-Type"] = = nil {
                urlRequest.headers.update(.contentType("application/json"))
            }

            urlRequest.httpBody = data
        } catch {
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
        }

        return urlRequest
    }
}
Copy the code

Bool NSNumber (Bool); Bool NSNumber (Bool);

extension NSNumber {
    fileprivate var isBool: Bool {
        // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of
        // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22).
        String(cString: objCType) = = "c"}}Copy the code

ParameterEncoder

The protocol is simple, and there is only one way to encode a parameter type into a URLRequest that is compatible with the Encodable protocol.

public protocol ParameterEncoder {
    
    func encode<Parameters: Encodable> (_ parameters: Parameters? .into request: URLRequest) throws -> URLRequest
}
Copy the code

ParameterEncoding can be encoded into Request. The encoding position can be controlled, but the requirements for parameters are different:

ParameterEncoding Specifies that the parameter is a dictionary type. The value of the dictionary is Any. When it is encoded as a URL query String, it is directly converted to string. ParameterEncoder is required for Encodable objects. JSONEncoder is required for JSON coding which is not in the standard format. The ParameterEncoder is required for Encodable objects. The URLEncodedFormEncoder coder is used to encode the URL Query string

So it’s ok to encode both types for Encodable dictionaries. Parameter = [” A “: 1, “b”: 2]

There are also two default implementations for JSON encoding and URL Query string encoding:

1.JSONParameterEncoder encodes JSON data

Use the JSONEncoder of the system to encode data, you can control the format of JSON, ios11 also supports sorting by key (JSON dictionary is unordered), the implementation method is relatively simple:

open class JSONParameterEncoder: ParameterEncoder {
    //MARK: static computed properties used to quickly create objects
    
    /// The default type, initialized using the default JSONEncoder, will compress the JSON format
    public static var `default`: JSONParameterEncoder { JSONParameterEncoder()}// use standard JSON format for output
    public static var prettyPrinted: JSONParameterEncoder {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted

        return JSONParameterEncoder(encoder: encoder)
    }

    Ios11 supports output JSON sorted by key
    @available(macOS 10.13.iOS 11.0.tvOS 11.0.watchOS 4.0.*)
    public static var sortedKeys: JSONParameterEncoder {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .sortedKeys

        return JSONParameterEncoder(encoder: encoder)
    }
    
    // MARK: Attributes and initializations

    /// The JSONEncoder used to encode parameters
    public let encoder: JSONEncoder

    public init(encoder: JSONEncoder = JSONEncoder()) {
        self.encoder = encoder
    }
    
    /// implement protocol encoding method:
    open func encode<Parameters: Encodable> (_ parameters: Parameters? .into request: URLRequest) throws -> URLRequest {
        // Get parameters
        guard let parameters = parameters else { return request }

        var request = request

        do {
            // Encode the parameters as JSON data
            let data = try encoder.encode(parameters)
            / / into the body
            request.httpBody = data
            / / set the content-type
            if request.headers["Content-Type"] = = nil {
                request.headers.update(.contentType("application/json"))}}catch {
            // Parsing json exceptions throws an error
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
        }

        return request
    }
}
Copy the code

2. URLEncodedFormParameterEncoder coding url query string data

Url encoding, using Destination to determine whether to encode the URL query or body, encoding data using the URLEncodedFormEncoder class

open class URLEncodedFormParameterEncoder: ParameterEncoder {
    /// ParameterEncoding position, same as ParameterEncoding
    public enum Destination {
        case methodDependent
        case queryString
        case httpBody

        func encodesParametersInURL(for method: HTTPMethod) -> Bool {
            switch self {
            case .methodDependent: return [.get, .head, .delete].contains(method)
            case .queryString: return true
            case .httpBody: return false}}}// MARK: Initializes the object by default, using the URLEncodedFormEncoder default parameter. The encoding location is determined by method
    public static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder()}/// URLEncodedFormEncoder object used to encode data
    public let encoder: URLEncodedFormEncoder

    /// encode position
    public let destination: Destination

    public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) {
        self.encoder = encoder
        self.destination = destination
    }
    
    // Implement the protocol encoding parameter method
    open func encode<Parameters: Encodable> (_ parameters: Parameters? .into request: URLRequest) throws -> URLRequest {
        // Get parameters
        guard let parameters = parameters else { return request }

        var request = request
        
        // Make sure the URL exists first
        guard let url = request.url else {
            throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
        }
        // Then make sure method exists
        guard let method = request.method else {
            let rawValue = request.method?.rawValue ?? "nil"
            throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue)))
        }
        // According to the encoding position, the encoding operation
        if destination.encodesParametersInURL(for: method),
           var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
            //url query
            // Here is the formatting to write detailed comments (blow swift!)
            let query: String = try Result<String.Error> {// Initialize Request(with a closure that can throw an exception)
                try encoder.encode(parameters)// Encode parameters
            }
            .mapError {// Encoding error is converted to AFError
                AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0))
            }
            .get()//get can get success data, if error, will throw an exception
            Querystring [String?] [String?] Array, and then compactMap strip out nil, and combine it with ampersands
            let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands()
            components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString
            // The url cannot be empty
            guard let newURL = components.url else {
                throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
            }

            request.url = newURL
        } else {
            / / set the content-type
            if request.headers["Content-Type"] = = nil {
                request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))}// Encode and throw in the body teasing: trailing closure + write line is too uncomfortable to read
            request.httpBody = try Result<Data.Error> {
                try encoder.encode(parameters)
            }
            .mapError {
                AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0))
            }
            .get()
        }

        return request
    }
}
Copy the code

URLEncodedFormEncoder The core class used to encode data into A URL Query String

  • The class is declared final, does not allow inheritance, and only allows the behavior to be controlled by parameters at initialization.
  • This class defines a number of data types that control coding behavior and sets these types at initialization time
  • Encoding uses a custom _URLEncodedFormEncoder internal type property that implements the Encoder protocol. To encode data
  • The encoded data store object is the URLEncodedFormComponent enumeration, which can hold string, array, and object represented by tuple arrays. Using tuple arrays to represent object types keeps the parameters in order.
  • Holds a URLEncodedFormContext context property that holds the URLEncodedFormComponent to hold data that is passed as context recursive encoding
  • Finally, a URLEncodedFormSerializer is implemented to serialize the encoded URLEncodedFormComponent data into a Query String

URLEncodedFormEncoder This custom encoder is a bit complicated and will be explained in the next article

The above is purely personal understanding, unavoidably wrong, if found there is a mistake, welcome to comment pointed out, will be the first time to modify, very grateful ~