Forward navigation:

Alamofire source learning directory collection

Introduction to the

Response and parsing consists of two core modules:

  • Response.swift: Defines two generic structures: DataResponse and DownloadResponse to encapsulate the Response to the request, including: URLRequest, HTTPURLResponse, response raw Data (Data format), serialized Data, request indicator Data. DownloadResponse also includes the download file URL, breakpoint continuation of data information and other data functions are only encapsulation, and serialized data or error transformation operations, used in the completion of the request callback as a parameter.
  • ResponseSerialization. Swift, the request to the data serialization, use a generic protocol for isolation, are free to specify the serialized data type Alamofire built-in several serialization type, can be used directly, It can also implement its own serializer. Before serializing Data, it can also preprocess the original Data and encapsulate it as an interface DataPreprocessor. The parser can hold the preprocessing protocol object and use the preprocessor to preprocess the Data before parsing it.

Response Request Response encapsulation:

There are two responses:

  • Common request response (including DataRequest, UploadRequest)
  • Download request response (Data will be saved to a local file, so a file URL will be held, and the downloaded data will be temporarily saved for resumable processing in case of download interruption)

Usage scenario: When using Alamofire request, the parameter in the request completion callback is the Response object:

// Send the request and parse the response data into JSOn
AF.request("").responseJSON(completionHandler: {
    (resp : AFDataResponse<Any>) in
    resp.data// Raw response Data (Data? Type)
    resp.value// Serialized generic objects (Any? Type)
    resp.error// Serialize or request the error object (AFError? Type)
})
Copy the code

Definition:

Both structures are wrapped with generics, which are serialized data types and errors in serialization:

public struct DataResponse<Success.Failure: Error>
public struct DownloadResponse<Success.Failure: Error> // We then generically wrap the two types to encapsulate the error asAFErrorTo theAlamofireReturn when requested:public typealias AFDataResponse<Success> = DataResponse<Success.AFError>
public typealias AFDownloadResponse<Success> = DownloadResponse<Success.AFError>
Copy the code

DataResponse

Encapsulates the response header, response data, and corresponding response request, and defines the generic Success and Error types to serialize the response data or Error to the corresponding types. Extended a bunch of methods to transform the response data and errors just to encapsulate the response from DataRequest to UploadRequest

Private attributes and initializations:
    / / / request
    public let request: URLRequest?

    / / / response headers
    public let response: HTTPURLResponse?

    /// respond to raw data
    public let data: Data?

    /// Final request metrics, including request time, redirection times, etc
    public let metrics: URLSessionTaskMetrics?

    /// The time taken to serialize the response
    public let serializationDuration: TimeInterval

    // serialize the result of the response
    public let result: Result<Success.Failure>

    // get serialized data quickly (possibly nil)
    public var value: Success? { result.success }

    /// quick serialization error
    public var error: Failure? { result.failure }

    public init(request: URLRequest? .response: HTTPURLResponse? .data: Data? .metrics: URLSessionTaskMetrics? .serializationDuration: TimeInterval.result: Result<Success.Failure>) {
        self.request = request
        self.response = response
        self.data = data
        self.metrics = metrics
        self.serializationDuration = serializationDuration
        self.result = result
    }
Copy the code
The extension implements String description and debugString description

Because the result attribute type is the result generic, the result description is returned directly in the string description, regardless of whether the type is.success or.failure, it is automatically converted to the corresponding parameter type description

extension DataResponse: CustomStringConvertible.CustomDebugStringConvertible {
    /// discard the description of result directly, regardless of success or failure
    public var description: String {
        "\(result)"
    }

    /// Debug Description
    public var debugDescription: String {
        guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" }

        let requestDescription = DebugDescription.description(of: urlRequest)

        let responseDescription = response.map { response in
            let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers)

            return "" "\(DebugDescription.description(of: response))
                \(responseBodyDescription.indentingNewlines())"" "
        } ?? "[Response]: None"

        let networkDuration = metrics.map { "\ [$0.taskInterval.duration)s" } ?? "None"

        return "" "\(requestDescription)
        \(responseDescription)
        [Network Duration]: \(networkDuration)
        [Serialization Duration]: \(serializationDuration)s
        [Result]: \(result)"" "}}Copy the code
The extension adds a type transformation

Since result is a generic result that holds success data/error information, four transformation methods have been added to return a new DataResponse, and the success error type can be transformed to another type:

extension DataResponse {
    /// 1. Use a closure to transform the Success type of result to the new type. The closure transform does not allow errors to be thrown and does not handle failures
    public func map<NewSuccess> (_ transform: (Success) - >NewSuccess) -> DataResponse<NewSuccess.Failure> {
        DataResponse<NewSuccess.Failure>(request: request,
                                          response: response,
                                          data: data,
                                          metrics: metrics,
                                          serializationDuration: serializationDuration,
                                          result: result.map(transform))
    }

    /// 2. Use a closure to convert the Success type of the original result to the new type. The closure can throw an exception
    public func tryMap<NewSuccess> (_ transform: (Success) throws -> NewSuccess) -> DataResponse<NewSuccess.Error> {
        DataResponse<NewSuccess.Error>(request: request,
                                        response: response,
                                        data: data,
                                        metrics: metrics,
                                        serializationDuration: serializationDuration,
                                        result: result.tryMap(transform))
    }

    /// 3. Use closures to change the Failure of the original result to a new Failure, corresponding to the map above
    public func mapError<NewFailure: Error> (_ transform: (Failure) - >NewFailure) -> DataResponse<Success.NewFailure> {
        DataResponse<Success.NewFailure>(request: request,
                                          response: response,
                                          data: data,
                                          metrics: metrics,
                                          serializationDuration: serializationDuration,
                                          result: result.mapError(transform))
    }

    /// 4. Use closure to handle Failure of result, throw exception, corresponding to tryMap above
    public func tryMapError<NewFailure: Error> (_ transform: (Failure) throws -> NewFailure) -> DataResponse<Success.Error> {
        DataResponse<Success.Error>(request: request,
                                     response: response,
                                     data: data,
                                     metrics: metrics,
                                     serializationDuration: serializationDuration,
                                     result: result.tryMapError(transform))
    }
}
Copy the code

DownloadResponse

The DataResponse encapsulates the response, but only the DownloadRequest response, because it needs to encapsulate the file-related data, so there are more properties related to the downloaded file

Properties and initialization

DataResponse has two more attributes:

    public let request: URLRequest?
    
    public let response: HTTPURLResponse?

    /// Download the file address
    public let fileURL: URL?

    // When the download is cancelled, the downloaded data is passed to the upper layer to decide whether to save it for breakpoint continuation
    public let resumeData: Data?

    public let metrics: URLSessionTaskMetrics?

    public let serializationDuration: TimeInterval

    public let result: Result<Success.Failure>

    public var value: Success? { result.success }

    public var error: Failure? { result.failure }

    public init(request: URLRequest? .response: HTTPURLResponse? .fileURL: URL? .resumeData: Data? .metrics: URLSessionTaskMetrics? .serializationDuration: TimeInterval.result: Result<Success.Failure>) {
        self.request = request
        self.response = response
        self.fileURL = fileURL
        self.resumeData = resumeData
        self.metrics = metrics
        self.serializationDuration = serializationDuration
        self.result = result
    }
Copy the code
Other extensions

Similar to DataResponse, DownloadResponse has two extensions, one for String descriptions, and the other for four types

Debug helper types and extensions

Two auxiliary types are used when implementing the DebugString protocol in both Response:

  • The auxiliary enumeration of DebugDescription, without any case, encapsulates only four static methods, which are used to output debugging information. DebugDescription can be printed using the debugPrint function or the. DebugDescription property
private enum DebugDescription {
    static func description(of request: URLRequest) -> String {
        let requestSummary = "\(request.httpMethod!) \(request)"
        let requestHeadersDescription = DebugDescription.description(for: request.headers)
        let requestBodyDescription = DebugDescription.description(for: request.httpBody, headers: request.headers)

        return """
        [Request]: \(requestSummary)
            \(requestHeadersDescription.indentingNewlines())
            \(requestBodyDescription.indentingNewlines())"" "
    }

    static func description(of response: HTTPURLResponse) -> String {
        """
        [Response]:
            [Status Code]: \(response.statusCode)
            \(DebugDescription.description(for: response.headers).indentingNewlines())"" "
    }

    static func description(for headers: HTTPHeaders) -> String {
        guard !headers.isEmpty else { return "[Headers]: None" }

        let headerDescription = "\(headers.sorted())".indentingNewlines()
        return """
        [Headers]:
            \(headerDescription)"" "
    }

    static func description(for data: Data? .headers: HTTPHeaders.allowingPrintableTypes printableTypes: [String] = ["json"."xml"."text"].maximumLength: Int = 100 _000) -> String {
        guard let data = data, !data.isEmpty else { return "[Body]: None" }

        guard
            data.count < = maximumLength,
            printableTypes.compactMap({ headers["Content-Type"]?.contains($0) }).contains(true)
        else { return "[Body]: \(data.count) bytes" }

        return """
        [Body]:
            \(String(decoding: data, as: UTF8.self)
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .indentingNewlines())"" "}}Copy the code
  • The extension String turns a regular line feed into an indent line feed, a simple substitution
extension String {
    fileprivate func indentingNewlines(by spaceCount: Int = 4) -> String {
        let spaces = String(repeating: "", count: spaceCount)
        return replacingOccurrences(of: "\n", with: "\n\(spaces)")}}Copy the code

ResponseSerialization ResponseSerialization

A core of Alamofire is Session+Request and its subclasses, managing Session, creating URLRequest, sending URLSessionTask, managing the association between Request and Task, handling various events in Request and error handling. When the request is completed, the response data needs to be parsed. By default, the response data can be parsed into JSON, String, Decodable, etc. You can also implement your own interface to parse the response data into other interfaces. So another core is ResponseSerialization response parsing. In the whole network request process, the core is two points: 1. Send request, 2. Process request

Features:

  • The interface abstraction is used for serialization of response data. The interface is very simple: the input parameters are request, Task, data, error, and the output parameters are the objects after generic serialization. The interface only cares about the serialized input and output parameters, and the specific serialized data types and serialization methods are encapsulated by each serializer.
  • Before serialization, Alamofire also defines a DataPreprocessor interface, which is used to preprocess the original response Data line. The input parameter is Data, and the output parameter is Data, and exceptions can be thrown. Several built-in serializers of Alamofire all hold Data preprocessor. They preprocess Data before starting to parse serialized Data, catch possible errors and throw them, and then start subsequent serialization operations after completion of preprocessing
  • After a Request is created using Session, no Task is actually sent. There are three opportunities for sending a Task:
    1. Session.startImmediately = true, the first time a request calls the Response *** method to add a response resolution callback, it will call resume after the callback to send the request
    2. Session.startimmediately = true, DataStreamRequest is created, asInputStream() is called, and InputStream is returned. Immediately after the IOStream pair is created and bound, resume is called to send the request
    3. Session.startimmediately = false, the created request will not be sent automatically, you need to manually call request.resume() to send the request after calling the Response *** method to add the parse callback
  • Alamofire has multiple built-in serializers, each of which has two types: DataResponse and DownResponse. After the definition of each serializer is completed, it will be followed by the extension of Request to add corresponding response method for Request
  • Calling the.response*** method on Request essentially adds a serialization callback that will be stored in the Responseserialcallback array of the Request’s MutableState property. When the Request is complete, Request calls serialization callbacks one by one. Therefore, you can perform multiple resonse operations on a Request that you create, parsing the same Request multiple times and getting different types of response data
  • DataStreamResponse parsing is different from DataResponse and DownloadResponse and is explained separately at the end

Serialization interface definition

Serialization is a generic serialization datatype, a simple serialization method, divided into Data Download and Data Download, but not in:

  1. The original Data type of Data serialized is Data? , Download serialized data type is fileURL?
  2. Download serialization operation, in fact, can be seen as: first read fileURL as Data, and then use Data serialization method to process. So if a serializer can serialize both tasks, it can implement the default for Download (more on that later).
// serialize the request + response header + response Data+ error using the interface method into a generic response object
/// DataResponse only
public protocol DataResponseSerializerProtocol {
    /// Serialized data type
    associatedtype SerializedObject
    
    func serialize(request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws -> SerializedObject
}

/// use the DownloadResponse request
public protocol DownloadResponseSerializerProtocol {
    /// Serialized data type
    associatedtype SerializedObject
    
    func serializeDownload(request: URLRequest? .response: HTTPURLResponse? .fileURL: URL? .error: Error?). throws -> SerializedObject
}
Copy the code

Composite serialization interface protocolResponseSerializer

Data and Download serialization protocols are combined together, and the Data preprocessing protocol object is added to allow empty Data HTTPMethod and response code.

By default, if the response Data returned is empty, it is treated as a failed request, but in reality, such as a Head request, there is no response Data. The response code is 204 (No Content), and 205 (Reset Content) also has only the response header and No response Data. However, the ResponseSerializer does not treat the request as a failure, so the ResponseSerializer adds two Set properties to hold HTTPMethod and response codes that allow the response Data to be null.

/// a combined protocol that serializes both DataResponse and DownloadResponse
public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol {
    /// Data preprocessor, used to preprocess Data before serialization
    var dataPreprocessor: DataPreprocessor { get }
    /// `HTTPMethod`s for which empty response bodies are considered appropriate.
    /// allow request Method with null response data (default HEAD request response data nil)
    var emptyRequestMethods: Set<HTTPMethod> { get }
    Error code allowing null response data (default [204, 205] response code data nil)
    var emptyResponseCodes: Set<Int> { get}}Copy the code

Response data preprocessing protocolDataPreprocessor

The protocol is very simple, there is only one method, the input parameter and the output parameter are Data, the implementation object can preprocess the input parameter Data, perform the add, delete, change and check operation, return the processed Data, and then continue to parse, the method allows to throw exceptions, when the exception is thrown, the upper layer will catch the exception and return as AFError encapsulation

/// The protocol used to preprocess Data, Alamofire has two default implementations
public protocol DataPreprocessor {
    If an exception is thrown, it is encapsulated as AFError
    func preprocess(_ data: Data) throws -> Data
}
Copy the code

Two response data preprocessors built into Alamofire

Alamofire implements two preprocessor structs with permissions public and can be used directly

PassthroughPreprocessor

The ResponseSerializer protocol is used as the default preprocessor for the ResponseSerializer protocol

/// The default passthrough processor does nothing
public struct PassthroughPreprocessor: DataPreprocessor {
    public init(a) {}
    public func preprocess(_ data: Data) throws -> Data { data }
}
Copy the code
Google’s XSSI data preprocessor, GoogleXSSIPreprocessor

Remove the data prefix)]}’,\n six characters. This preprocessor is only defined and is not used in Alamofire.

/// preprocess Google XSSI data, remove the first 6 characters
public struct GoogleXSSIPreprocessor: DataPreprocessor {
    public init(a) {}

    public func preprocess(_ data: Data) throws -> Data {
        (data.prefix(6) = = Data(")]} ',\n".utf8)) ? data.dropFirst(6) : data
    }
}
Copy the code

extensionResponseSerializerProtocol, add default implementation

Added a default implementation of the three properties for ResponseSerializer and added three shortcuts to determine whether the request and response data are allowed to be null


/// Extend the protocol to add default implementations for the three attributes
extension ResponseSerializer {
    /// The default Data preprocessor is pass-through and does nothing
    public static var defaultDataPreprocessor: DataPreprocessor { PassthroughPreprocessor()}// by default, the Method of empty Data is allowed to be HEAD
    public static var defaultEmptyRequestMethods: Set<HTTPMethod> { [.head] }
    // by default, the status codes for empty Data are 204 and 205
    public static var defaultEmptyResponseCodes: Set<Int> {[204.205]}/// The default implementation of three properties
    public var dataPreprocessor: DataPreprocessor { Self.defaultDataPreprocessor }
    public var emptyRequestMethods: Set<HTTPMethod> { Self.defaultEmptyRequestMethods }
    public var emptyResponseCodes: Set<Int> { Self.defaultEmptyResponseCodes }

    /// Check whether the request allows the response Data to be null
    The optional map and flatMap methods take arguments of data types and return optional types. If the value is not nil, the closure is called. If it is nil, it returns nil
    public func requestAllowsEmptyResponseData(_ request: URLRequest?). -> Bool? {
        request.flatMap { $0.httpMethod }
            .flatMap(HTTPMethod.init)
            .map { emptyRequestMethods.contains($0)}}// Check whether the status code of the response allows Data to be null
    public func responseAllowsEmptyResponseData(_ response: HTTPURLResponse?). -> Bool? {
        response.flatMap { $0.statusCode }
            .map { emptyResponseCodes.contains($0)}}/// combine the above two detection methods
    public func emptyResponseAllowed(forRequest request: URLRequest? .response: HTTPURLResponse?). -> Bool {
        (requestAllowsEmptyResponseData(request) = = true) || (responseAllowsEmptyResponseData(response) = = true)}}Copy the code

The Download serialization protocol is extended to add Data parsing implementation

When a serializer implements both the Data serialization interface and the Download serialization interface, the behavior of the Download serialization can be regarded as:

  1. First read the file out using Data
  2. Then use Data’s serialization method to process

Therefore, you can provide a default implementation for the Download resolution method.

Note that the entire file is read directly into memory with Data. Note that if the file is too large, the memory will explode.

/ / / to meet DownloadResponseSerializerProtocol and provide default implementation meet DataResponseSerializerProtocol protocol type
/ / / as long as the Data read from the file first can be a serialized Data to achieve DataResponseSerializerProtocol method
/// Read a large file directly
extension DownloadResponseSerializerProtocol where Self: DataResponseSerializerProtocol {
    public func serializeDownload(request: URLRequest? .response: HTTPURLResponse? .fileURL: URL? .error: Error?). throws -> Self.SerializedObject {
        // If there is an error, throw it directly to the upper layer to catch
        guard error = = nil else { throw error! }
        /// the file URL is empty
        guard let fileURL = fileURL else {
            throw AFError.responseSerializationFailed(reason: .inputFileNil)
        }
        /// read Data, catch read error and convert throw
        let data: Data
        do {
            data = try Data(contentsOf: fileURL)
        } catch {
            throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))
        }
        / / / go DataResponseSerializerProtocol serialization logic, catch the error and sell them
        do {
            return try serialize(request: request, response: response, data: data, error: error)
        } catch {
            throw error
        }
    }
}
Copy the code

Define built-in serializers and add serialization methods

  • Alamofire adds seven serialization methods to the Request extension and provides five built-in serializers (fileUrl, Data, String, JSON, Decodable) that can be used directly or customized by the interface.
  • Each serializer includes a definition type, followed by an extended Response *** method added to DataRequest and DownloadRequest.
1. Raw data serialization (two types)

Since the original Data is Data or Fileurl, we don’t define serializers anymore. Instead, we add extended serialized response methods to both requests, and parse callbacks easily:

  1. Encapsulate the AFResult type and the Success type is Data? Or URL? type
  2. Assemble the DataResponse with Success type AFResult in 1
  3. Notifies the event listener that the serialization response is complete
  4. Notifies the Request that the serialized response is complete
extension DataRequest {
    
    /// Normal parsing, just encapsulating Data
    @discardableResult
    public func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDataResponse<Data? - > >)Void) -> Self {
        / / appendResponseSerializer is a parameter for closure, there is no return value method, following the closure of writing at first glance it looks easy question
        appendResponseSerializer {
            // Start parsing the response, which must be done in the parsing queue
            // Encapsulate Data directly without parsing by default
            let result = AFResult<Data? >(value:self.data, error: self.error)
            // Response parsing is complete

            // The behavior after parsing needs to be completed in the corresponding queue
            self.underlyingQueue.async {
                / / packaging
                let response = DataResponse(request: self.request,
                                            response: self.response,
                                            data: self.data,
                                            metrics: self.metrics,
                                            serializationDuration: 0,
                                            result: result)
                // Tell the listener
                self.eventMonitor?.request(self, didParseResponse: response)
                // The callback is complete
                self.responseSerializerDidComplete { queue.async { completionHandler(response) } }
            }
        }

        return self}}extension DownloadRequest {
    Download data is processed as a callback in the form of a file URL, which is an optional type
    @discardableResult
    public func response(queue: DispatchQueue = .main,
                         completionHandler: @escaping (AFDownloadResponse<URL? - > >)Void)
        -> Self {
        appendResponseSerializer {
            // Start work that should be on the serialization queue.
            let result = AFResult<URL? >(value:self.fileURL, error: self.error)
            // End work that should be on the serialization queue.

            self.underlyingQueue.async {
                let response = DownloadResponse(request: self.request,
                                                response: self.response,
                                                fileURL: self.fileURL,
                                                resumeData: self.resumeData,
                                                metrics: self.metrics,
                                                serializationDuration: 0,
                                                result: result)

                self.eventMonitor?.request(self, didParseResponse: response)

                self.responseSerializerDidComplete { queue.async { completionHandler(response) } }
            }
        }

        return self}}Copy the code
2. Use generic serialization protocol objects to handle responses (both types)
  • This response processing method is the most basic processing method, the following several are the corresponding serialization protocol object to call this method, if you use your own implementation of the serializer, you can also use this method to serialize data, without the need to write their own serialization method.
  • When serialization protocol objects are used to process data, the time required to process data is calculated in the serializationQueue of the Request. After data processing is complete, the data is returned to the underlyingQueue for subsequent operations
  • The data received during serialization may have an error (request error) or an error occurred during serialization. If the result of the original data processing is failure, the retry logic is started
  • Finally, the relevant callback is executed to complete
extension DataRequest {
    /// Use custom parsers to parse
    @discardableResult
    public func response<Serializer: DataResponseSerializerProtocol> (queue: DispatchQueue = .main,
                                                                     responseSerializer: Serializer.completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject- > >)Void)
        -> Self {
        appendResponseSerializer {
            // Start parsing the response, which must be done in the response parsing queue, because there is parsing, so start timing
            let start = ProcessInfo.processInfo.systemUptime
            // Use the parameter parser to parse the data, catch the error and convert to AFError
            let result: AFResult<Serializer.SerializedObject> = Result {
                try responseSerializer.serialize(request: self.request,
                                                 response: self.response,
                                                 data: self.data,
                                                 error: self.error)
            }.mapError { error in
                error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error)))
            }
            // Use the app startup time difference to calculate the parsing time
            let end = ProcessInfo.processInfo.systemUptime
            // Parsing is complete

            // return to the Request internal queue to continue processing
            self.underlyingQueue.async {
                // Assemble DataResponse, Success type is the generic SerializedObject type in the serialization protocol
                let response = DataResponse(request: self.request,
                                            response: self.response,
                                            data: self.data,
                                            metrics: self.metrics,
                                            serializationDuration: end - start,
                                            result: result)
                
                // Tell the listener
                self.eventMonitor?.request(self, didParseResponse: response)

                guard let serializerError = result.failure, let delegate = self.delegate else {
                    // If the parse succeeds, go directly to complete the logic
                    self.responseSerializerDidComplete { queue.async { completionHandler(response) } }
                    return
                }
                // Parsing error, ready to retry
                delegate.retryResult(for: self, dueTo: serializerError) { retryResult in
                    
                    Nil means to retry
                    var didComplete: (() -> Void)?

                    defer {
                        // Process with defer
                        if let didComplete = didComplete {
                            self.responseSerializerDidComplete { queue.async { didComplete() } }
                        }
                    }
                    
                    // Determine whether to retry according to the retryResult parameter
                    switch retryResult {
                    case .doNotRetry:// Complete without retry
                        didComplete = { completionHandler(response) }

                    case let .doNotRetryWithError(retryError):// Replace error with no retry
                        // Initialize result with the new retryError
                        let result: AFResult<Serializer.SerializedObject> = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError"))

                        // Encapsulate a new Response
                        let response = DataResponse(request: self.request,
                                                    response: self.response,
                                                    data: self.data,
                                                    metrics: self.metrics,
                                                    serializationDuration: end - start,
                                                    result: result)

                        didComplete = { completionHandler(response) }

                    case .retry, .retryWithDelay:/ / try again
                        delegate.retryRequest(self, withDelay: retryResult.delay)
                    }
                }
            }
        }

        return self}}extension DownloadRequest {
    // use a custom serializer to handle the download response
    @discardableResult
    public func response<Serializer: DownloadResponseSerializerProtocol> (queue: DispatchQueue = .main,
                                                                         responseSerializer: Serializer.completionHandler: @escaping (AFDownloadResponse<Serializer.SerializedObject- > >)Void)
        -> Self {
        appendResponseSerializer {
            // Start serializing results, timing
            let start = ProcessInfo.processInfo.systemUptime
            let result: AFResult<Serializer.SerializedObject> = Result {
                try responseSerializer.serializeDownload(request: self.request,
                                                         response: self.response,
                                                         fileURL: self.fileURL,
                                                         error: self.error)
            }.mapError { error in
                error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error)))
            }
            let end = ProcessInfo.processInfo.systemUptime
            // The serialization result is complete

            self.underlyingQueue.async {
                let response = DownloadResponse(request: self.request,
                                                response: self.response,
                                                fileURL: self.fileURL,
                                                resumeData: self.resumeData,
                                                metrics: self.metrics,
                                                serializationDuration: end - start,
                                                result: result)

                self.eventMonitor?.request(self, didParseResponse: response)

                guard let serializerError = result.failure, let delegate = self.delegate else {
                    self.responseSerializerDidComplete { queue.async { completionHandler(response) } }
                    return
                }
                / / try again
                delegate.retryResult(for: self, dueTo: serializerError) { retryResult in
                    var didComplete: (() -> Void)?

                    defer {
                        if let didComplete = didComplete {
                            self.responseSerializerDidComplete { queue.async { didComplete() } }
                        }
                    }

                    switch retryResult {
                    case .doNotRetry:
                        didComplete = { completionHandler(response) }

                    case let .doNotRetryWithError(retryError):
                        let result: AFResult<Serializer.SerializedObject> = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError"))

                        let response = DownloadResponse(request: self.request,
                                                        response: self.response,
                                                        fileURL: self.fileURL,
                                                        resumeData: self.resumeData,
                                                        metrics: self.metrics,
                                                        serializationDuration: end - start,
                                                        result: result)

                        didComplete = { completionHandler(response) }

                    case .retry, .retryWithDelay:
                        delegate.retryRequest(self, withDelay: retryResult.delay)
                    }
                }
            }
        }

        return self}}Copy the code
3. Download file URL processing (DownloadResonse only available)
  • Just a simple serializer that only DownloadResonse is used to handle fileUrl usage
  • Because fileUrl is an optional type and can be nil in the default DownloadResponse, the serializer can be used to filter the case of nil, throw AFError, and return fileUrl of an optional type.
  • The DownloadRequest extension adds the responseURL method
public struct URLResponseSerializer: DownloadResponseSerializerProtocol {
    
    public init(a) {}

    public func serializeDownload(request: URLRequest? .response: HTTPURLResponse? .fileURL: URL? .error: Error?). throws -> URL {
        guard error = = nil else { throw error! }

        guard let url = fileURL else {
            // Raise error if file URL is nil
            throw AFError.responseSerializationFailed(reason: .inputFileNil)
        }

        return url
    }
}

extension DownloadRequest {
    Download data is handled as a file URL callback. The file URL must not be nil
    @discardableResult
    public func responseURL(queue: DispatchQueue = .main,
                            completionHandler: @escaping (AFDownloadResponse<URL- > >)Void) -> Self {
        response(queue: queue, responseSerializer: URLResponseSerializer(), completionHandler: completionHandler)
    }
}
Copy the code
4.Data format serializer (two types)
  • DataResponseSerializer serializer implements the ResponseSerializer protocol and is qualified as final, which cannot be inherited
  • Data can be preprocessed to process empty Data
  • Serialized Data is of type Data
  • Extend DataRequest with DownloadRequest to add responseData serialization method
public final class DataResponseSerializer: ResponseSerializer {
    // Implement three properties of the ResponseSerializer protocol
    public let dataPreprocessor: DataPreprocessor
    public let emptyResponseCodes: Set<Int>
    public let emptyRequestMethods: Set<HTTPMethod>

    public init(dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
                emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
                emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods) {
        self.dataPreprocessor = dataPreprocessor
        self.emptyResponseCodes = emptyResponseCodes
        self.emptyRequestMethods = emptyRequestMethods
    }

    public func serialize(request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws -> Data {
        guard error = = nil else { throw error! }

        guard var data = data, !data.isEmpty else {
            // If data is null, (including nil, null data), call the protocol extension's method to check whether null data is allowed
            guard emptyResponseAllowed(forRequest: request, response: response) else {
                // It is not allowed to throw an error on empty Data
                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
            }
            // If null Data is allowed, return an empty Data object
            return Data()}// Preprocess Data
        data = try dataPreprocessor.preprocess(data)
        // Return type Data
        return data
    }
}

extension DataRequest {
    /// Extend DataRequest to add parse response callbacks
    @discardableResult
    public func responseData(queue: DispatchQueue = .main,
                             dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
                             emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
                             emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods,
                             completionHandler: @escaping (AFDataResponse<Data- > >)Void) -> Self {
        // Call the general method above using serialization protocol object resolution
        response(queue: queue,
                 responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                            emptyResponseCodes: emptyResponseCodes,
                                                            emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }
}

extension DownloadRequest {
    / / / same as above
    @discardableResult
    public func responseData(queue: DispatchQueue = .main,
                             dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
                             emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
                             emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods,
                             completionHandler: @escaping (AFDownloadResponse<Data- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                            emptyResponseCodes: emptyResponseCodes,
                                                            emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }
}
Copy the code
5.String format preprocessor
  • The StringResponseSerializer serializer implements the ResponseSerializer protocol and is qualified as final, which is not inheritable
  • String encoding format can be specified. The default is. IsoLatin1. Priority: Manual Settings > Server Back > Default isoLatin1
  • Data can be preprocessed to process empty Data
  • Serialized Data is of type Data
  • Extend DataRequest with DownloadRequest to add responseString serialization method
public final class StringResponseSerializer: ResponseSerializer {
    public let dataPreprocessor: DataPreprocessor
    /// String encoding format
    public let encoding: String.Encoding?
    public let emptyResponseCodes: Set<Int>
    public let emptyRequestMethods: Set<HTTPMethod>

    public init(dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
                encoding: String.Encoding? = nil.emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
                emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods) {
        self.dataPreprocessor = dataPreprocessor
        self.encoding = encoding
        self.emptyResponseCodes = emptyResponseCodes
        self.emptyRequestMethods = emptyRequestMethods
    }

    public func serialize(request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws -> String {
        guard error = = nil else { throw error! }

        // Check for empty data, throw an exception or return an empty string
        guard var data = data, !data.isEmpty else {
            guard emptyResponseAllowed(forRequest: request, response: response) else {
                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
            }

            return ""
        }
        
        // Preprocess Data
        data = try dataPreprocessor.preprocess(data)

        // String encoding format
        var convertedEncoding = encoding
        
        if let encodingName = response?.textEncodingName, convertedEncoding = = nil {
            // No encoding format is specified and the server has a return encoding format
            convertedEncoding = String.Encoding(ianaCharsetName: encodingName)
        }
        // Actual encoding format: Manual > Server Back > default isoLatin1
        let actualEncoding = convertedEncoding ?? .isoLatin1

        // Encoding fails to throw an error
        guard let string = String(data: data, encoding: actualEncoding) else {
            throw AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding))
        }

        return string
    }
}

extension DataRequest {
    /// Add a parsing method
    @discardableResult
    public func responseString(queue: DispatchQueue = .main,
                               dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
                               encoding: String.Encoding? = nil.emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
                               emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
                               completionHandler: @escaping (AFDataResponse<String- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                              encoding: encoding,
                                                              emptyResponseCodes: emptyResponseCodes,
                                                              emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }
}

extension DownloadRequest {
    / / / same as above
    @discardableResult
    public func responseString(queue: DispatchQueue = .main,
                               dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
                               encoding: String.Encoding? = nil.emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
                               emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
                               completionHandler: @escaping (AFDownloadResponse<String- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                              encoding: encoding,
                                                              emptyResponseCodes: emptyResponseCodes,
                                                              emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }
}
Copy the code
6.JOSN serializer (two types)
  • JSONResponseSerializer serializer implements the ResponseSerializer protocol and is qualified as final, which cannot be inherited
  • The JSON read format can be specified. AllowFragments by default
  • Parse Data using the system’s JSONSerialization
  • Data can be preprocessed to process empty Data
  • Serialized Data is of type Data
  • Extend DataRequest with DownloadRequest to add responseJSON serialization method
public final class JSONResponseSerializer: ResponseSerializer {
    public let dataPreprocessor: DataPreprocessor
    public let emptyResponseCodes: Set<Int>
    public let emptyRequestMethods: Set<HTTPMethod>
    /// JSON reads the format
    public let options: JSONSerialization.ReadingOptions

    public init(dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
                emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
                emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
                options: JSONSerialization.ReadingOptions = .allowFragments) {
        self.dataPreprocessor = dataPreprocessor
        self.emptyResponseCodes = emptyResponseCodes
        self.emptyRequestMethods = emptyRequestMethods
        self.options = options
    }

    // Implement the protocol parsing method
    public func serialize(request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws -> Any {
        guard error = = nil else { throw error! }

        // Decide to process empty Data
        guard var data = data, !data.isEmpty else {
            guard emptyResponseAllowed(forRequest: request, response: response) else {
                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
            }

            return NSNull()}/ / pretreatment
        data = try dataPreprocessor.preprocess(data)

        do {
            // serialize JSON
            return try JSONSerialization.jsonObject(with: data, options: options)
        } catch {
            // Catch a throw error
            throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
        }
    }
}

extension DataRequest {
    /// Add the response method
    @discardableResult
    public func responseJSON(queue: DispatchQueue = .main,
                             dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
                             emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
                             emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
                             options: JSONSerialization.ReadingOptions = .allowFragments,
                             completionHandler: @escaping (AFDataResponse<Any- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: JSONResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                            emptyResponseCodes: emptyResponseCodes,
                                                            emptyRequestMethods: emptyRequestMethods,
                                                            options: options),
                 completionHandler: completionHandler)
    }
}

extension DownloadRequest {
    / / / same as above
    @discardableResult
    public func responseJSON(queue: DispatchQueue = .main,
                             dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
                             emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
                             emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
                             options: JSONSerialization.ReadingOptions = .allowFragments,
                             completionHandler: @escaping (AFDownloadResponse<Any- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: JSONResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                            emptyResponseCodes: emptyResponseCodes,
                                                            emptyRequestMethods: emptyRequestMethods,
                                                            options: options),
                 completionHandler: completionHandler)
    }
}
Copy the code
7.Decodable format serializer (two types)

Because Decodable is a protocol type, it is slightly more complicated

  • The empty data protocol type EmptyResponse is declared, with a static method emptyValue() to return the empty data type
  • Empty is declared to implement the Codable and EmptyResponse protocols, and can be used as the default EmptyResponse in cidr. (This is not used in cidR. The only use is when initializing StreamRequest using the Encodable protocol type parameter.) Used as the default null parameter)
  • Declare DataDecoder decoder protocol, used to decode Data type response Data to Decoable protocol type, because the decoding method and the system’s default JSONDecoder and PropertyListDecoder decoder decoding name, So just declare the two system decoders to implement the DataDecoder protocol can be directly used
  • Define DecodableResponseSerializer serializer implementation ResponseSerializer agreement and modification for final, an inheritance, and declares the generic type T realize Decodable agreement as a result the serialization type
  • Holds DataDecoder decoder protocol properties, used to decode Data, default to JSONDecoder
  • Data can be preprocessed to process empty Data
  • Serialized Data is of type Data
  • Extend DataRequest with DownloadRequest to add responseDecodable serialization method
public final class DecodableResponseSerializer<T: Decodable> :ResponseSerializer {
    public let dataPreprocessor: DataPreprocessor
    /// The decoder used for decoding, the default is system JSONDecoder decoder
    public let decoder: DataDecoder
    public let emptyResponseCodes: Set<Int>
    public let emptyRequestMethods: Set<HTTPMethod>

    public init(dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor,
                decoder: DataDecoder = JSONDecoder(),
                emptyResponseCodes: Set<Int> = DecodableResponseSerializer.defaultEmptyResponseCodes,
                emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer.defaultEmptyRequestMethods) {
        self.dataPreprocessor = dataPreprocessor
        self.decoder = decoder
        self.emptyResponseCodes = emptyResponseCodes
        self.emptyRequestMethods = emptyRequestMethods
    }
    // Implement the ResponseSerializer protocol parsing method
    public func serialize(request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws -> T {
        guard error = = nil else { throw error! }

        // Process empty data
        guard var data = data, !data.isEmpty else {
            guard emptyResponseAllowed(forRequest: request, response: response) else {
                throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
            }
            // Get the null data constant from the parse result type T
            guard let emptyResponseType = T.self as? EmptyResponse.Type.let emptyValue = emptyResponseType.emptyValue() as? T else {
                // Parse result type T that does not follow EmptyResponse throws an error
                throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)"))}// Returns the default null data
            return emptyValue
        }
        // Preprocess Data
        data = try dataPreprocessor.preprocess(data)

        do {
            / / decoding Data
            return try decoder.decode(T.self, from: data)
        } catch {
            // Catch a thrown exception
            throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error))
        }
    }
}

extension DataRequest {
    / / / add
    @discardableResult
    public func responseDecodable<T: Decodable> (of type: T.Type = T.self.queue: DispatchQueue = .main,
                                                dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
                                                decoder: DataDecoder = JSONDecoder(),
                                                emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
                                                emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
                                                completionHandler: @escaping (AFDataResponse<T- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                                 decoder: decoder,
                                                                 emptyResponseCodes: emptyResponseCodes,
                                                                 emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }
}

extension DownloadRequest {
    / / / same as above
    @discardableResult
    public func responseDecodable<T: Decodable> (of type: T.Type = T.self.queue: DispatchQueue = .main,
                                                dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
                                                decoder: DataDecoder = JSONDecoder(),
                                                emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
                                                emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
                                                completionHandler: @escaping (AFDownloadResponse<T- > >)Void) -> Self {
        response(queue: queue,
                 responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor,
                                                                 decoder: decoder,
                                                                 emptyResponseCodes: emptyResponseCodes,
                                                                 emptyRequestMethods: emptyRequestMethods),
                 completionHandler: completionHandler)
    }
}
Copy the code

DataStreamRequest Serializes request response data

The corresponding Data above comes from DataRequest and DownloadRequest, and the requests are all completed in one time. DataStreamRequest’s response is special and it continuously receives Data. Therefore, when DataStreamResponse is serialized, It only needs to parse each received Data, which is simpler than the above two

Serialization protocol

Again, protocol is used to decouple. The serialization protocol is also simple, with a generic result Data type and a serialization method that only parses Data, not Request and Response.

public protocol DataStreamSerializer {
    // serialize the result generics
    associatedtype SerializedObject
    /// serialize methods
    func serialize(_ data: Data) throws -> SerializedObject
}
Copy the code

Define built-in serializers and add serialization methods

Alamofire defines three built-in serializers and adds four serialization methods for StreamRequest.

  • There are fewer fileurl and JSON serializers than the Data and Download requests above
  • Serializers are defined together, and serialization methods are added at once, unlike Data and Download request serialization definitions.
  • Because there is no Request and Response to process, and no retry logic, the parser logic is very simple
1. DecodableStreamSerializer serializer decoding protocol type objects
  • Serialize Data to a generic object that complies with the Decodable protocol
  • Holds the DataDecoder generic decoder object. The default decoder is the system JSONDecoder
  • Hold preprocessor, can preprocess Data
  • The analytical logic is similar to the above
public struct DecodableStreamSerializer<T: Decodable> :DataStreamSerializer {
    /// Parse Data parser, default to system JSONDecoder
    public let decoder: DataDecoder
    /// Data preprocessor
    public let dataPreprocessor: DataPreprocessor

    public init(decoder: DataDecoder = JSONDecoder(), dataPreprocessor: DataPreprocessor = PassthroughPreprocessor()) {
        self.decoder = decoder
        self.dataPreprocessor = dataPreprocessor
    }
    / / parsing
    public func serialize(_ data: Data) throws -> T {
        / / pretreatment
        let processedData = try dataPreprocessor.preprocess(data)
        do {
            / / analytical Data
            return try decoder.decode(T.self, from: processedData)
        } catch {
            // Catch the exception and throw it
            throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error))
        }
    }
}
Copy the code
2. PassthroughStreamSerializer passthrough serializer
  • Do nothing to data, just pass through
  • Used as the default serializer
public struct PassthroughStreamSerializer: DataStreamSerializer {
    public func serialize(_ data: Data) throws -> Data { data }
}
Copy the code
3.StringStreamSerializer String serializer
  • Decode Data to string using UTF8
public struct StringStreamSerializer: DataStreamSerializer {
    public func serialize(_ data: Data) throws -> String {
        String(decoding: data, as: UTF8.self)}}Copy the code

Add a serialization method to DataStreamRequest

Each response*** method that adds a Request essentially adds a closure to the Request. Each time the Request receives Data, the Request will call this closure to process the Data, which is different from the above closure that only calls the Data Download Request once. The closure type DataStream adds is defined as an alias Handler, whose input parameter is aStream structure, where each Stream represents a data unit in the DataStream and can represent either a data event or a completion event. As the request is executed, the Handler is invoked repeatedly. After all parsing is complete, the Handler is processed

1. Add methods to process responses in Data format
  • The received data is not serialized, but the subsequent process is simply executed
  • A callback to handle streams is added to the Request, stored in the Mutable. Streams array
  • Finish the callback and encapsulates a callback, stored in a Mutable. EnqueuedCompletionEvents array, used to after all data parsing is complete, send complete data unit.
  • Add the responseStream method to DataStreamRequest
    @discardableResult
    public func responseStream(on queue: DispatchQueue = .main, stream: @escaping Handler<Data.Never>) -> Self {
        // Create the parse callback paser and append it to the parse callback queue of the Request
        let parser ={[unowned self] (data: Data) in
            queue.async {
                self.capturingError {
                    // Execute Handle to catch exceptions
                    try stream(.init(event: .stream(.success(data)), token: .init(self)))}// Update the status and check if it needs to be completed
                self.updateAndCompleteIfPossible()
            }
        }
        // Append the parse callback
        $streamMutableState.write { $0.streams.append(parser) }
        // Append the completion callback to Handle
        appendStreamCompletion(on: queue, stream: stream)

        return self
    }
Copy the code
2. Add DataStreamSerializer to use the generic serializer processing method

The overall logic is similar to the normal processing above, with more data parsing steps and notification listeners

  • The parsed data is the data type in the DataStreamSerializer generic serialization
  • Is the most basic parsing method, and both of the following parsing methods are dispatched to be called using the corresponding serializer instance
  • Parsing needs to be done in the serializationQueue queue
  • The eventMonitor event listener is notified when parsing is complete
  • Parse failure can decide whether to cancel the request according to automaticallyCancelOnStreamError attributes
  • Append the responseStream serialization method to DataStreamRequest
    @discardableResult
    public func responseStream<Serializer: DataStreamSerializer> (using serializer: Serializer.on queue: DispatchQueue = .main,
                                                                 stream: @escaping Handler<Serializer.SerializedObject.AFError>) -> Self {
        // Create the parser callback
        let parser ={[unowned self] (data: Data) in
            self.serializationQueue.async {
                // Start the parsing task in the parsing queue
                let result = Result { try serializer.serialize(data) }
                    .mapError { $0.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: $0)))}// Parsing is complete
                self.underlyingQueue.async {
                    self.eventMonitor?.request(self, didParseStream: result)
                    
                    // If the resolution fails and failure cancel is set, cancel the request
                    if result.isFailure, self.automaticallyCancelOnStreamError {
                        self.cancel()
                    }

                    queue.async {
                        self.capturingError {
                            try stream(.init(event: .stream(result), token: .init(self)))}// Update the status and check if it needs to be completed
                        self.updateAndCompleteIfPossible()
                    }
                }
            }
        }
        // Add to the callback array
        $streamMutableState.write { $0.streams.append(parser) }
        // Append the stream to complete the data
        appendStreamCompletion(on: queue, stream: stream)

        return self
    }
Copy the code
3. Add a serialization method that resolves to String
  • Decode Data to String using UTF8
  • Stream handles the error format of the callback input parameter type with Never instead of AFError, and does not fail when decoded to String by default
  • In theory, we should use the StringStreamSerializer serializer defined above, but in practice we don’t use it because the parsing operation is relatively simple and the stream handles different callback types. I’m just going to write it like I did with data
  • Append the responseStreamString method to DataStreamRequest
    @discardableResult
    public func responseStreamString(on queue: DispatchQueue = .main,
                                     stream: @escaping Handler<String.Never>) -> Self {
        let parser ={[unowned self] (data: Data) in
            self.serializationQueue.async {
                // Start work on serialization queue.
                let string = String(decoding: data, as: UTF8.self)
                // End work on serialization queue.
                self.underlyingQueue.async {
                    self.eventMonitor?.request(self, didParseStream: .success(string))

                    queue.async {
                        self.capturingError {
                            try stream(.init(event: .stream(.success(string)), token: .init(self)))}// Update the status and check if it needs to be completed
                        self.updateAndCompleteIfPossible()
                    }
                }
            }
        }

        $streamMutableState.write { $0.streams.append(parser) }
        appendStreamCompletion(on: queue, stream: stream)

        return self
    }
Copy the code
A way to update status and check if parsing is complete

When each Stream request receives Data, it is ready to parse the Data. The numberOfExecutingStreams corresponding to DataStreamRequest is added to the number of Stream callbacks it holds, indicating that these callbacks are all parseable. After each Stream is parseable, If numberOfExecutingStreams == 0 means that all callbacks have parsed the data, DataStreamRequest calls the completion callbacks stored in enqueuedCompletionEvents one by one, and removes them once they’re done. If new parse callbacks are added during parsing, The completion callbacks for the parse callback call are stored in enqueuedCompletionEvents and are executed one by one after all the flow callback processing is complete.

    private func updateAndCompleteIfPossible(a) {
        $streamMutableState.write { state in
            // The flow callback in the process is reduced by 1
            state.numberOfExecutingStreams - = 1

            guard state.numberOfExecutingStreams = = 0.!state.enqueuedCompletionEvents.isEmpty else {
                // If there is still a stream callback in progress, or the processing callback is not complete, return directly
                return
            }
            // All flow callbacks have been processed
            let completionEvents = state.enqueuedCompletionEvents
            // call it one by one
            self.underlyingQueue.async { completionEvents.forEach { $0()}}// Empty the saved completion callback
            state.enqueuedCompletionEvents.removeAll()
        }
    }
Copy the code
3+. Use the StringStreamSerializer serializer to handle the response

Now that we have defined the StringStreamSerializer String serializer, we can use the serializer directly for String parsing. The logic is much simpler:

    // Add a responseStreamString2 resolution method to DataStreamRequest
    @discardableResult
    public func responseStreamString2(on queue: DispatchQueue = .main,
                                      stream: @escaping Handler<String.AFError>) -> Self {
        responseStream(using: StringStreamSerializer(), on: queue, stream: stream)
    }
Copy the code

Although the Handle closure that stream handles has an AFError as an error argument, the StringStreamSerializer parsing method does not throw an error at all. This means that Handler<String, AFError> is the same as Handler<String, Never>.

4. Use DecodableStreamSerializer generic parser to handle the response
  • Using DecodableStreamSerializer generics serializer analytical Data
  • Add the responseStreamDecodable resolution method to DataStreamRequest
    @discardableResult
    public func responseStreamDecodable<T: Decodable> (of type: T.Type = T.self.on queue: DispatchQueue = .main,
                                                      using decoder: DataDecoder = JSONDecoder(),
                                                      preprocessor: DataPreprocessor = PassthroughPreprocessor(),
                                                      stream: @escaping Handler<T.AFError>) -> Self {
        responseStream(using: DecodableStreamSerializer<T>(decoder: decoder, dataPreprocessor: preprocessor),
                       stream: stream)
    }
Copy the code

conclusion

Adding the response*** response method to the Request extension essentially adds two core closures to the Request:

  • Parse closure: Used to parse Data (Data and Download requests are called only once, Stream requests are called multiple times)
  • Completion closure: Used to process the parse completion logic (Data and Download are added in the parse closure, Stream is added after the parse closure is added, and completion Bibb references the parse closure)

With all the nested closures involved, and with swift’s trailing closure approach, it seems difficult. The next section will summarize this in more detail.

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 ~