This article is a translation of Alamofire Advanced Usage. If necessary, please refer to the original text.

The content of this article is also saved to my GitHub repository. If you find it useful, you can give it a Star. Thank you very much!

Alamofire is built on URLSession and the URL loading system. To take full advantage of this framework, it is recommended that you become familiar with the concepts and functions of the underlying network.

Recommended reading:

  • URL Loading System Programming Guide
  • URLSession Class Reference
  • URLCache Class Reference
  • URLAuthenticationChallenge Class Reference

Session

Alamofire’s Session is roughly equivalent in responsibility to the URLSession instance it maintains: It provides apis to generate various Request subclasses that encapsulate different URLSessionTask subclasses, as well as various configurations that encapsulate all requests that the instance generates.

Session provides a singleton instance of default, and AF is actually session.default. Therefore, the following two statements are equivalent:

AF.request("https://httpbin.org/get")
Copy the code
let session = Session.default
session.request("https://httpbin.org/get")
Copy the code

Create a customSessionThe instance

Most applications will need to customize the behavior of their Session instances in various ways. The simplest way to do this is to use the following convenience initializer and store the results in a single instance used throughout the application.

public convenience init(
    configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
    delegate: SessionDelegate = SessionDelegate(),
    rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
    startRequestsImmediately: Bool = true.requestQueue: DispatchQueue? = nil.serializationQueue: DispatchQueue? = nil.interceptor: RequestInterceptor? = nil.serverTrustManager: ServerTrustManager? = nil.redirectHandler: RedirectHandler? = nil.cachedResponseHandler: CachedResponseHandler? = nil.eventMonitors: [EventMonitor] =[])
Copy the code

This initializer allows you to customize all the basic behavior of a Session.

useURLSessionConfigurationcreateSession

To customize the behavior of the underlying URLSession, you can provide a custom URLSessionConfiguration instance. Advice from URLSessionConfiguration. Af. The default instance, because it provides the default add Alamofire Accept Encoding, Accept the Language and the user-agent headers, However, you can use any URLSessionConfiguration.

let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false

let session = Session(configuration: configuration)
Copy the code

URLSessionConfiguration is not the recommended location for setting Authorization or Content-Type headers. Instead, you can add them to the Request using the provided Headers APIs, ParameterEncoder, or RequestAdapter.

As Apple states in its documentation, modifying the URLSessionConfiguration property after the instance has been added to the URLSession (or, in the case of Alamofire, used to initialize the Session) has no effect.

SessionDelegate

The SessionDelegate instance encapsulates all handling of the various URLSessionDelegate and related protocol callbacks. The SessionDelegate also acts as the SessionStateDelegate for each Request generated by Alamofire, allowing the Request to import state indirectly from the Session instance that created them. SessionDelegate can be customized with a specific FileManager instance that will be used for any disk access, such as accessing files to be uploaded via UploadRequest or downloaded via DownloadRequest.

let delelgate = SessionDelegate(fileManager: .default)
Copy the code

startRequestsImmediately

By default, the Session calls Resume () on the Request immediately after adding at least one response handler. Setting startRequestsImmediately to false requires manually calling the Resume () method on all requests.

let session = Session(startRequestsImmediately: false)
Copy the code

SessionDispatchQueue

By default, Session instances use a single DispatchQueue for all asynchronous work. This includes the underlyingQueue of the URLSession’s delegate OperationQueue, which is used for all URLRequest creation, all response serialization, and all internal Session and Request state changes. If the performance analysis shows that the bottleneck is URLRequest creation or response serialization, a separate DispatchQueue can be provided for each work area of the Session.

let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")

let session = Session(
    rootQueue: rootQueue,
    requestQueue: requestQueue,
    serializationQueue: serializationQueue
 )
Copy the code

Any custom rootqueues provided must be serial queues, but RequestQueues and SerializationQueues can be serial or parallel queues. Serial queues are usually recommended unless performance analysis shows that work is delayed, in which case making the queues parallel may help improve overall performance.

addRequestInterceptor

Alamofire’s RequestInterceptor protocol (RequestAdapter & RequestRetrier) provides important and powerful request adaptation and retry capabilities. It can be used at the Session and Request levels. For more details on RequestInterceptor and the various implementations included with Alamofire, such as RetryPolicy, see below.

let policy = RetryPolicy(a)let session = Session(interceptor: policy)
Copy the code

addServerTrustManager

Alamofire’s ServerTrustManager class encapsulates the mapping between domain names and instances of types that follow the ServerTrustEvaluating protocol, which provides the ability to customize sessions to handle TLS security. This includes using certificate and public key fixation and certificate revocation checking. For more information, see the section on ServerTrustManager and ServerTrustEvaluating. Initializing the ServerTrustManger is as simple as providing a mapping between the domain name and the type of computation to be performed:

let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let session = Session(serverTrustManager: manager)
Copy the code

For more information on evaluating server trust, see the detailed documentation below.

addRedirectHandler

Alamofire’s RedirectHandler protocol customizes the handling of HTTP redirect responses. It can be used at the Session and Request levels. Alamofire includes the Redirector type that follows the RedirectHandler protocol and provides simple control over redirection. For more information about the redirection handler, see the detailed documentation below.

let redirector = Redirector(behavior: .follow)
let session = Session(redirectHandler: redirector)
Copy the code

addCachedResponseHandler

Alamofire’s CachedResponseHandler protocol customizes the caching of responses, which can be used at the Session and Request levels. Alamofire contains the ResponseCacher type, which follows the CachedResponseHandler protocol and provides simple control over response caching. For more information, see the detailed documentation below.

let cacher = ResponseCacher(behavior: .cache)
let session = Session(cachedResponseHandler: cacher)
Copy the code

addEventMonitor

Alamofire’s EventMonitor protocol provides powerful insights into events within Alamofire. It can be used to provide logging and other event-based features. The Session receives an array of instances that comply with the EventMonitor protocol when initialized.

let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
    debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
Copy the code

fromURLSessionCreate an instance

In addition to the convenience initializer mentioned earlier, you can initialize a Session directly from URLSession. However, there are several requirements to keep in mind when using this initializer, so convenience initializers are recommended. These include:

  • Alamofire does not support configuration for use in the backgroundURLSession. Initialize theSession, which causes a runtime error.
  • You must createSessionDelegateInstance and treat it asURLSessiondelegateAnd pass toSessionInitializer for.
  • Must be customOperationQueueAs aURLSessiondelegateQueue. This queue must be a serial queue, and it must have a standbyDispatchQueue, and must be theDispatchQueueAs itsrootQueuePassed to theSession.
let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = rootQueue
let delegate = SessionDelegate(a)let configuration = URLSessionConfiguration.af.default
let urlSession = URLSession(configuration: configuration,
                            delegate: delegate,
                            delegateQueue: queue)
let session = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)
Copy the code

request

Each request executed by Alamofire is encapsulated by a specific class, DataRequest, UploadRequest, and DownloadRequest. Each of these classes encapsulates functionality specific to each type of Request, but DataRequest and DownloadRequest inherit from a common parent of Request (UploadRequest inherits from DataRequest). Request instances are never created directly, but are generated automatically from the Session through one of the various Request methods.

Request pipeline

Once it is created using the initial parameters of the Request subclass or URLRequestConvertible, it is passed through the series of steps that make up the Alamofire Request pipeline. For successful requests, these include:

  1. Initial parameters, such as HTTP methods, HEADERS, and parameters, are encapsulated internallyURLRequestConvertibleIn the value. If you pass it directlyURLRequestConvertibleValue, the value remains unchanged when used.
  2. rightURLRequestConvertibleValue is calledasURLRequest(), create the first oneURLRequestValue. This value will be passed toRequestAnd stored inrequestsIn the.
  3. If there are anySessionRequest RequestAdapterRequestInterceptor, the previously created one is usedURLRequestCall them. Then adjust the postURLRequestPassed to theRequestAnd stored inrequestsIn the.
  4. SessioncallRequestTo create theURLSessionTask, based onURLRequestPerform network requests.
  5. completeURLSessionTaskAnd collectURLSessionTaskMetricsLater,RequestWill perform itsValidator.
  6. Request to execute any response handlers that have been attached, such asresponseDecodable.

In any of these steps, a failure can be indicated by creating or receiving an Error value, which can then be passed to the associated Request. For example, with the exception of steps 1 and 4, all of the above steps can create an Error, which is then passed to response Handlers or can be retried. Here are some examples of how you may or may not fail in the entire request pipeline.

  • Parameter encapsulation cannot fail.
  • callasURLRequest()When anyURLRequestConvertibleValues can be created incorrectly. This allows initial validation of variousURLRequestAttribute or parameter encoding failed.
  • RequestAdapterIt is possible to fail during the adaptive process, possibly due to a lack of authorization token.
  • URLSessionTaskThe creation cannot fail.
  • URLSessionTaskMay complete incorrectly for a variety of reasons, including network availability and cancellation. theseErrorThe value will be passed back toRequest.
  • Handlers can produce any error, usually due to an invalid response or other analysis error.

Once an error is passed to Request, Request will try to run any RequestRetrier associated with the Session or Request. If any RequestRetrier chooses to retry the Request, the complete pipe is run again. The RequestRetrier also generates errors, but these errors do not trigger retries.

Request

Although Request does not encapsulate any specific type of Request, it contains the state and functionality common to all requests performed by Alamofire. This includes:

state

All Request types contain the concept of state, representing major events in the Request lifecycle.

public enum State {
    case initialized
    case resumed
    case suspended
    case cancelled
    case finished
}
Copy the code

The request is started in the.initialized state after creation. Request can be suspended, resumed, and cancelled by invoking the appropriate lifecycle methods.

  • resume()Restores or starts the requested network traffic. ifstartRequestsImmediatelytrue, adding response Handlers toRequestThis function is automatically called.
  • suspend()Suspends or suspends a request and its network traffic. In this stateRequestYou can go on, but onlyDownloadRequestTo continue transmitting data. otherRequestWill start again.
  • cancel()Cancel the request. Once in this state, it cannot be restored or suspendedRequest. callcancel()Will be usedAFError.explicitlyCancelledInstance set requestederrorProperties. If aRequestRecovered and not cancelled later, it will arrive after all response validators and response serializers have run.finishedState. However, if the request reaches.finishedState after additional response serializers are added to the request, it will be converted back.resumedState and execute the network request again.

The progress of

To track the progress of the Request, Request provides the uploadProgress and downloadProgress properties and the uploadProgress and downloadProgress methods based on closures. As with all closures based Request APIs, the progress APIs can be linked to other methods outside of the Request. Like other closures based APIs, they should be added to the request before any response handlers (such as responseDecodable) are added.

AF.request(.)
    .uploadProgress { progress in
        print(progress)
    }
    .downloadProgress { progress in
        print(progress)
    }
    .responseDecodable(of: SomeType.self) { response in
        debugPrint(response)
    }
Copy the code

Importantly, not all Request subclasses are able to accurately report their progress, or may have other dependencies to report their progress.

  • You can determine the upload progress in the following ways:
    • Provided as an upload bodyUploadRequestDataObject length.
    • Through asUploadRequestUpload body provides the length of the file on disk.
    • By requestContent-LengthThe value of the header, if set manually.
  • There is only one requirement for download progress:
    • The server response must containContent-LengthThe header. Unfortunately,URLSessionThere may be other unrecorded requirements for progress reporting that prevent accurate progress reporting.

To deal with the callback

Alamofire’s Directhandler protocol provides control and customization of Request redirection processing. In addition to each Session RedirectHandler, each Request can be assigned its own RedirectHandler, and this handler will override any RedirectHandler provided by the Session.

let redirector = Redirector(behavior: .follow)
AF.request(.)
    .redirect(using: redirector)
    .responseDecodable(of: SomeType.self) { response in
        debugPrint(response)
    }
Copy the code

Note: Only one RedirectHandler can be set per Request. Attempting to set more than one will cause a runtime exception.

Custom cache

Alamofire’s CachedResponseHandler protocol provides control and customization of response caching. In addition to CachedResponseHandlers for each Session, each Request can be assigned its own CachedResponseHandler, And this handler will override any CachedResponseHandler provided by the Session.

let cacher = Cacher(behavior: .cache)
AF.request(.)
    .cacheResponse(using: cacher)
    .responseDecodable(of: SomeType.self) { response in
        debugPrint(response)
    }
Copy the code

Note: Only one CachedResponseHandler can be set for a Request. Attempting to set more than one will cause a runtime exception.

Credentials

To take advantage of the automatic credential processing provided by URLSession, Alamofire provides each Request API that allows URLCredential instances to be automatically added to requests. This includes a convenient API for HTTP authentication using a user name and password, as well as any URLCredential instances.

Adding credentials to automatically reply to any HTTP authentication challenge is simple:

AF.request(.)
    .authenticate(username: "[email protected]", password: "password")
    .responseDecodable(of: SomeType.self) { response in
        debugPrint(response)
    }
Copy the code

Note: This mechanism only supports HTTP authentication hints. If a request requires an Authentication header for all requests, it should be provided directly, either as part of the request, or through a RequestInterceptor.

In addition, adding URLCredential is equally simple:

let credential = URLCredential(.)
AF.request(.)
    .authenticate(using: credential)
    .responseDecodable(of: SomeType.self) { response in
        debugPrint(response)
    }
Copy the code

RequestURLRequest

Each network Request made by Request is ultimately encapsulated in a URLRequest value created by various parameters passed to one of the Session Request methods. Request will keep copies of these URlRequests in its Requests array property. These values include both the initial URLRequest created from the passed parameters and any URLRequest created by RequestInterceptors. However, this array does not include URLRequest executed on behalf of the URLSessionTask issued by Request. To check these values, the Tasks attribute allows access to all urlsessionTasks executed by the Request.

URLSessionTask

In many ways, the various Request subclasses act as wrappers around URLSessionTask, providing specific apis for interacting with specific types of tasks. These tasks are visible on the Request instance through the Tasks array property. This includes the initial task created for the Request, as well as any subsequent tasks created as part of the retry process, one task at a time.

The response

After the Request is complete, each Request may have an HTTPURLResponse value available. This value is available only if the request has not been cancelled and no network request has failed to be issued. Also, if the request is retried, only the last response is available. The intermediate response can be obtained from URLSessionTasks in the Tasks property.

URLSessionTaskMetrics

Alamofire collects URLSessionTaskMetrics values for each URLSessionTask executed by Request. These values are stored in the Metrics property, and each value corresponds to the URLSessionTask in the Tasks in the same index.

URLSessionTaskMetrics are also accessible from various Alamofire response types, such as DataResponse. Such as:

AF.request(.)
    .responseDecodable(of: SomeType.self) { response in {
        print(response.metrics)
    }
Copy the code

DataRequest

DataRequest is a subclass of Request that encapsulates the Urlssession Datatask, downloading the server response to Data stored in memory. Therefore, it is important to recognize that large downloads can adversely affect system performance. For these types of downloads, it is recommended to use DownloadRequest to save the data to disk.

Other states

The DataRequest has several attributes in addition to those provided by Request. These include Data (which is the cumulative data of the server response) and convertible (which is the URLRequestConvertible used when creating a DataRequest), which contains the original parameters to create the instance.

validation

By default, DataRequest does not validate the response. Instead, a call to validate() must be added to it to verify that various attributes are valid.

public typealias Validation = (URLRequest? .HTTPURLResponse.Data?). ->Result<Void.Error>
Copy the code

By default, adding validate() ensures that the response status code is at 200.. <300, and the content-Type of the response matches the Accept of the request. You can further customize Validation by passing the Validation closure:

AF.request(.)
    .validate { request, response, data in
        .
    }
Copy the code

UploadRequest

UploadRequest is a subclass of DataRequest that encapsulates urlssession UploadTask and uploads Data, files on disk, or InputStream to a remote server.

Other states

UploadRequest has several attributes in addition to those provided by DataRequest. These include a FileManager instance to customize access to the disk when uploading a file, and an Upload, which encapsulates a URLRequestConvertible value to describe the request and a Uploadable value to determine the type of upload to perform.

DownloadRequest

DownloadRequest is a concrete subclass of Request that encapsulates the URLSessionDownloadTask and downloads the response data to disk.

Other states

DownloadRequest has several properties in addition to those provided by Request. These include resumeData (which is generated when DownloadRequest is cancelled) and fileURL (which is the URL to download the file once the download is complete).

cancel

In addition to supporting the cancel() method provided by Request, DownloadRequest also includes cancel(producingResumeData shouldProduceResumeData: Bool), optionally setting the resumeData attribute on cancellation, if possible, and cancel(byProducingResumeData completionHandler: @escaping (_ data: data?). -> Void), which feeds the generated recovery data to the closure passed in.

AF.download(.)
    .cancel { resumeData in
        .
    }
Copy the code

validation

DownloadRequest supports a slightly different version of validation than DataRequest and UploadRequest because its data is downloaded to disk.

public typealias Validation = (_ request: URLRequest? ._ response: HTTPURLResponse._ fileURL: URL?).Copy the code

You must use the provided fileURL to access the downloaded Data rather than directly accessing the downloaded Data. Otherwise, DownloadRequest’s validator functions the same as DataRequest’s.

useRequestInterceptorAdjust and retry the request

Alamofire’s RequestInterceptor protocol (composed of the RequestAdapter and RequestRetrier protocols) supports powerful per-session and per-request capabilities. These include an authentication system in which a commonly used HEADERS is added to each Request and the Request is retried when authorization expires. In addition, Alamofire includes a built-in RetryPolicy type that can be easily retried when a request fails due to a variety of common network errors.

RequestAdapter

Alamofire’s RequestAdapter protocol allows each URLRequest executed by a Session to be examined and modified before it is sent over the network. A very common use of adapters is to add the Authorization header to a request after a particular type of authentication.

func adapt(_ urlRequest: URLRequest.for session: Session.completion: @escaping (Result<URLRequest.Error- > >)Void)
Copy the code

Its parameters include:

  • urlRequest: Originally from the parameter or used to create the requestURLRequestConvertibleThe value created byurlRequest.
  • session: creates the calling adapterRequestSession.
  • completion: an asynchronous Completion handler that must be invoked to indicate that the adapter has completed. Its asynchronous nature makes it possibleRequestAdapterThe ability to access asynchronous resources from the network or disk before the request is sent over the network. To providecompletionThe closure of theResultCan return with modifiedURLRequest.successValue, or return with an associated error.failureValue, which is then used to make the request fail. For example, addAuthorizationThe header needs to be modifiedURLRequestAnd then callcompletion.
let accessToken: String

func adapt(_ urlRequest: URLRequest.for session: Session.completion: @escaping (Result<URLRequest.Error- > >)Void) {
    var urlRequest = urlRequest
    urlRequest.headers.add(.authorization(bearer: accessToken))

    completion(.success(urlRequest))
}
Copy the code

RequestRetrier

Alamofire’s RequestRetrier protocol allows the retry of a request that encounters an error during execution. This includes errors that occur at any stage of Alamofire’s request pipeline.

The RequestRetrier protocol has only one method:

func retry(_ request: Request.for session: Session.dueTo error: Error.completion: @escaping (RetryResult) - >Void)
Copy the code

Its parameters include:

  • request: Encountered an errorRequest.
  • sessionManagement:RequestSession.
  • error: triggers the retryError“, usually oneAFError.
  • completion: the asynchronous Completion handler that must be invoked to representRequestWhether to retry. It must be passed in when calledRetryResult.

The RetryResult type represents the result of any logic implemented in the RequestRetrier. Defined as:

/// Outcome of determination whether retry is necessary.
public enum RetryResult {
    /// Retry should be attempted immediately.
    case retry
    /// Retry should be attempted after the associated `TimeInterval`.
    case retryWithDelay(TimeInterval)
    /// Do not retry.
    case doNotRetry
    /// Do not retry due to the associated `Error`.
    case doNotRetryWithError(Error)}Copy the code

For example, if the request is idempotent, the RetryPolicy type of Alamofire will automatically retry the request that failed due to some kind of network error.

open func retry(_ request: Request.for session: Session.dueTo error: Error.completion: @escaping (RetryResult) - >Void) {
    if request.retryCount < retryLimit,
       let httpMethod = request.request?.method,
       retryableHTTPMethods.contains(httpMethod),
       shouldRetry(response: request.response, error: error) {
        let timeDelay = pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale
        completion(.retryWithDelay(timeDelay))
    } else {
        completion(.doNotRetry)
    }
}
Copy the code

security

Using secure HTTPS connections when communicating with servers and Web services is an important step in protecting sensitive data. By default, Alamofire receives the same automatic TLS certificate and certificate chain validation as URLSession. While this ensures the validity of the certificate chain, it does not protect against man-in-the-middle (MITM) attacks or other potential vulnerabilities. To mitigate MITM attacks, applications that process sensitive customer data or financial information should be anchored using a certificate or public key provided by Alamofire’s ServerTrustEvaluating protocol.

useServerTrustManagerServerTrustEvaluatingEvaluating server trust

ServerTrustEvaluting

The ServerTrustEvaluting protocol provides a way to perform trust evaluation for any type of server. It has only one method:

func evaluate(_ trust: SecTrust.forHost host: String) throws
Copy the code

This method provides the SecTrust value and host String received from the underlying URLSession and provides the opportunity to perform various evaluations.

Alamofire includes many different types of trust estimators that provide composable control over the evaluation process:

  • DefaultTrustEvaluator: Uses the default server trust assessment, while allowing you to control whether to validate the host provided by the challenge.
  • RevocationTrustEvaluator: Checks the status of the received certificate to ensure that it has not been revoked. This is usually not done on every request because it requires network request overhead.
  • PinnedCertificatesTrustEvaluator: Verifies server trust using the provided certificate. If a fixed certificate matches a server certificate, the server trust is considered valid. This evaluator can also accept self-signed certificates.
  • PublicKeysTrustEvaluator: Verifies server trust using the provided public key. If a fixed public key matches a server certificate public key, the server trust is considered valid.
  • CompositeTrustEvaluator: Evaluate aServerTrustEvaluatingAn array of values is successful only if the values in all arrays are successful. This type can be used for composition, for example,RevocationTrustEvaluatorPinnedCertificatesTrustEvaluator.
  • DisabledEvaluator: This estimator should only be used in debugging scenarios because it disables all evaluations that will always assume that any server trust is valid. This estimator should not be used in a production environment!

ServerTrustManager

The ServerTrustManager is responsible for storing internal mappings of ServerTrustEvaluating values to specific hosts. This allows Alamofire to evaluate each host using a different evaluator.

let evaluators: [String: ServerTrustEvaluating] = [
    // By default, certificates included in the App Bundle are automatically fixed.
    "cert.example.com": PinnedCertificatesTrustEvalutor(),
    By default, the public key from the certificate contained in the app Bundle is automatically used.
    "keys.example.com": PublicKeysTrustEvalutor(),]let manager = ServerTrustManager(evaluators: serverTrustPolicies)
Copy the code

This ServerTrustManager will have the following behavior:

  • cert.example.comCertificates will always be fixed with default and host authentication enabled, so the following conditions need to be met to allow a TLS handshake to succeed:
    • The certificate chainMust beEffective.
    • The certificate chainMust beContains a fixed certificate.
    • Questioning the hostMust beMatches the host in the leaf certificate of the certificate chain.
  • keys.example.comPublic key fixation will always be used with default and host authentication enabled, so the following conditions need to be met to allow a TLS handshake to succeed:
    • The certificate chainMust beEffective.
    • The certificate chainMust beContains a fixed public key.
    • Questioning the hostMust beMatches the host in the certificate in the certificate chain.
  • Requests to other hosts will generate an error because the server trust manager requires that all hosts be evaluated by default.
subclassServerTrustPolicyManager

If you find yourself needing more flexible server trust policy matching behavior (such as wildcard domain names), subclass ServerTrustManager and override the serverTrustEvaluator(forHost:) method with your own custom implementation.

final class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
    override func serverTrustEvaluator(forHost host: String) -> ServerTrustEvaluating? {
        var policy: ServerTrustPolicy?

        // Implement your custom domain matching behavior...

        return policy
    }
}
Copy the code

App Transport Security

With App Transport Security (ATS) added in iOS 9, using a custom ServerTrustManager with multiple ServerTrustEvaluating objects probably doesn’t work. You may have encountered this problem if you continue to see CFNetwork SSLHandshake failed (-9806) error. Apple’s ATS system overrides the entire challenge system, unless you configure ATS Settings in your application’s PLIST to disable enough of them to allow your application to evaluate server trust. If you encounter this problem (self-signed certificates are highly likely), you can fix it by adding the NSAppTransportSecurity setting to info.plist. You can use the — ATS-Diagnostics option of the NSCURL tool to perform a series of tests on the host to see which ATS overrides might be required.

Use self-signed certificates on the local network

If you are trying to connect to a server running on a local host and using a self-signed certificate, you need to add the following to info.plist.

<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsLocalNetworking</key>
        <true/>
    </dict>
</dict>
Copy the code

According to apple documentation, setting NSAllowsLocalNetworking to YES allows local resources to be loaded without having to disable ATS for the rest of the application.

Custom caching and redirection handling

URLSession allows you to customize caching and redirection behavior using the URLSessionDataDelegate and URLSessionTaskDelegate methods. Alamofire renders these customization points as CachedResponseHandler and RedirectHandler protocols.

CachedResponseHandler

The CachedResponseHandler protocol allows control of caching HTTP responses into URLCache instances associated with the Session making the request. The protocol has only one method:

func dataTask(_ task: URLSessionDataTask.willCacheResponse response: CachedURLResponse.completion: @escaping (CachedURLResponse?). ->Void)
Copy the code

As you can see from the method signature, this control only applies to requests that use the underlying URLSessionDataTask for network transmission. These requests include DataRequest and UploadRequest (because Urlssession UploadTask is a subclass of Urlssession Datatask). Considering the cache has a very wide range of conditions in response, so it’s best to check the urlSession URLSessionDataDelegate method (_ : dataTask: willCacheResponse: completionHandler:) document. Once you consider using the response for caching, you can do a variety of valuable things:

  • By returningnil CachedURLResponseTo prevent full caching of responses.
  • Modify theCachedURLResponsestoragePolicyTo change where cached values are stored.
  • Modify the underlying layer directlyURLResponse, add or remove values.
  • Modify that is associated with the responseData(if any).

Alamofire contains a ResponseCacher type that follows the CachedResponseHandler protocol, making it easy to cache (or not cache) or modify the response. ResponseCacher accepts a Behavior value to control cache Behavior.

public enum Behavior {
    /// Stores the cached response in the cache.
    case cache
    /// Prevents the cached response from being stored in the cache.
    case doNotCache
    /// Modifies the cached response before storing it in the cache.
    case modify((URLSessionDataTask.CachedURLResponse) - >CachedURLResponse?). }Copy the code

ResponseCacher can be used on a Session and Request basis, as described above.

RedirectHandler

The RedirectHandler protocol allows you to control the redirection behavior of a specific Request. It has only one method:

func task(_ task: URLSessionTask.willBeRedirectedTo request: URLRequest.for response: HTTPURLResponse.completion: @escaping (URLRequest?). ->Void)
Copy the code

This method provides the opportunity to modify the URLRequest for the redirect or pass nil to disable the redirect completely. Alamofire provides a Redirector type that follows the RedirectHandler protocol, making it easy to follow, not follow, or modify redirection requests. The Redirector accepts an Behavior value to control the redirection Behavior.

public enum Behavior {
    /// Follow the redirect as defined in the response.
    case follow
    /// Do not follow the redirect defined in the response.
    case doNotFollow
    /// Modify the redirect request defined in the response.
    case modify((URLSessionTask.URLRequest.HTTPURLResponse) - >URLRequest?). }Copy the code

useEventMonitor

The EventMonitor protocol allows you to observe and examine a large number of internal Alamofire events. All these events including implementation by Alamofire URLSessionDelegate, URLSessionTaskDelegate and URLSessionDownloadDelegate method, and a large number of internal Request events. In addition to these events (which by default are null methods that do not work), the EventMonitor protocol also requires a DispatchQueue from which all events are scheduled to maintain performance. This DispatchQueue defaults to.main, but a dedicated serial queue is recommended for any custom consistent type.

Logging

Perhaps the biggest use of the EventMonitor protocol is for logging related events. A simple implementation might look like this:

final class Logger: EventMonitor {
    let queue = DispatchQueue(label: .)

    // Event called when any type of Request is resumed.
    func requestDidResume(_ request: Request) {
        print("Resuming: \(request)")}// Event called whenever a DataRequest has parsed a response.
    func request<Value> (_ request: DataRequest.didParseResponse response: DataResponse<Value.AFError>) {
        debugPrint("Finished: \(response)")}}Copy the code

This Logger type can be added to the Session as follows:

let logger = Logger(a)let session = Session(eventMonitors: [logger])
Copy the code

Create a request

As a framework, Alamofire has two main goals:

  1. Make networking requests for prototypes and tools easy to implement
  2. As a common basis for APP network requests

It achieves these goals by using powerful abstractions, providing useful defaults, and containing implementations of common tasks. However, once Alamofire is used beyond a few requests, it becomes necessary to move beyond the advanced, default implementation into application-specific behavior. Alamofire provides URLConvertible and URLRequestConvertible protocols to help with this customization.

URLConvertible

The URL can be constructed using a type that conforms to the URLConvertible protocol, and then the URL can be used internally to construct the URL request. By default, String, URL, and URLComponents follow the URLConvertible protocol, allowing any of them to be passed as a URL parameter to the Request, Upload, and Download methods:

let urlString = "https://httpbin.org/get"
AF.request(urlString)

let url = URL(string: urlString)!
AF.request(url)

let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
AF.request(urlComponents)
Copy the code

Applications that encourage meaningful interaction with Web applications have custom types that follow URLConvertible, which is a convenient way to map domain-specific models to server resources.

URLRequestConvertible

Types following the URLRequestConvertible protocol can be used to construct URLRequest. By default, URLRequest follows URLRequestConvertible, allowing it to be passed directly into the Request, Upload, and Download methods. Alamofire uses URLRevestExchange as the basis for all requests that flow through the request pipeline. Using URLRequest directly is the recommended way to create custom URLRequest in addition to the ParamterEncoder provided by Alamofire.

let url = URL(string: "https://httpbin.org/post")!
var urlRequest = URLRequest(url: url)
urlRequest.method = .post

let parameters = ["foo": "bar"]

do {
    urlRequest.httpBody = try JSONEncoder().encode(parameters)
} catch {
    // Handle error.
}

urlRequest.headers.add(.contentType("application/json"))

AF.request(urlRequest)
Copy the code

Applications that are encouraged to interact with Web applications in a meaningful way have custom types that follow URLRequestConvertible to ensure consistency of requested endpoints. This approach can be used to eliminate server side inconsistencies, provide type-safe routing, and manage other states.

Routing requests

As applications grow in size, it is important to adopt a common pattern when building a network stack. An important part of this design is how to route your requests. Alamofire URLConvertible and URLRequestConvertible protocols as well as the Router design pattern can help.

“Router” is the type that defines the “route” or request component. These components can include parts of the URLRequest, parameters required to make the request, and various Alamofire Settings for each request. A simple router might look like this:

enum Router: URLRequestConvertible {
    case get, post

    var baseURL: URL {
        return URL(string: "https://httpbin.org")!
    }

    var method: HTTPMethod {
        switch self {
        case .get: return .get
        case .post: return .post
        }
    }

    var path: String {
        switch self {
        case .get: return "get"
        case .post: return "post"}}func asURLRequest(a) throws -> URLRequest {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.method = method

        return request
    }
}

AF.request(Router.get)
Copy the code

More complex routers can include the parameters of the request. Any Encodable type can be used as a parameter using Alamofire’s ParameterEncoder protocol and included encoder:

enum Router: URLRequestConvertible {
    case get([String: String]), post([String: String])

    var baseURL: URL {
        return URL(string: "https://httpbin.org")!
    }

    var method: HTTPMethod {
        switch self {
        case .get: return .get
        case .post: return .post
        }
    }

    var path: String {
        switch self {
        case .get: return "get"
        case .post: return "post"}}func asURLRequest(a) throws -> URLRequest {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.method = method

        switch self {
        case let .get(parameters):
            request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
        case let .post(parameters):
            request = try JSONParameterEncoder().encode(parameters, into: request)
        }

        return request
    }
}
Copy the code

Routers can be extended to any number of endpoints with any number of configurable attributes, but once you reach a certain level of complexity, you should consider splitting a large Router into smaller ones as part of the API.

Response processing

Alamofire provides response processing through various response methods and the ResponseSerializer protocol.

Process unserialized responses

Both the DataRequest and DownloadRequest provide methods that allow response processing without calling any ResponseSerializer. This is most important when large files cannot be loaded into a DownloadRequest in memory.

// DataRequest
func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDataResponse<Data? - > >)Void) -> Self

// DownloadRequest
func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDownloadResponse<URL? - > >)Void) -> Self
Copy the code

As with all response Handlers, all serialization work (” None “in this case) is performed on the internal queue and the Completion Handler is invoked on the queue passed to the method. This means that it does not need to be dispatched back to the main queue by default. However, if you want to do any significant work in the Completion Handler, it is recommended that you pass the custom queue to the response method and dispatch back to the main queue in the Handler itself if necessary.

ResponseSerializer

ResponseSerializer protocol consists of DataResponseSerializerProtocol and DownloadResponseSerializerProtocol protocol. The ResponseSerializer combo is as follows:

public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol {
    /// The type of serialized object to be created.
    associatedtype SerializedObject

    /// `DataPreprocessor` used to prepare incoming `Data` for serialization.
    var dataPreprocessor: DataPreprocessor { get }
    /// `HTTPMethod`s for which empty response bodies are considered appropriate.
    var emptyRequestMethods: Set<HTTPMethod> { get }
    /// HTTP response codes for which empty response bodies are considered appropriate.
    var emptyResponseCodes: Set<Int> { get }

    func serialize(request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws -> SerializedObject
    func serializeDownload(request: URLRequest? .response: HTTPURLResponse? .fileURL: URL? .error: Error?). throws -> SerializedObject
}
Copy the code

By default, the serializeDownload method is implemented by reading the downloaded data from disk and calling serialize. Therefore, it might be more appropriate to implement custom processing for large downloads using the response(Queue :completionHandler:) method of the DownloadRequest mentioned above.

ResponseSerializer provides various default implementations for dataPreprocessor, emptyResponseMethods, and emptyResponseCodes, which can be customized in custom types, Such as the various ResponseSerializer that comes with Alamofire.

All ResponseSerializer is used via DataRequest and DownloadRequest methods:

// DataRequest
func response<Serializer: DataResponseSerializerProtocol> (queue: DispatchQueue = .main,
    responseSerializer: Serializer.completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject- > >)Void) -> Self

// DownloadRequest
func response<Serializer: DownloadResponseSerializerProtocol> (queue: DispatchQueue = .main,
    responseSerializer: Serializer.completionHandler: @escaping (AFDownloadResponse<Serializer.SerializedObject- > >)Void) -> Self
Copy the code

Alamofire includes several common response handlers, including:

  • responseData(queue:completionHandler)Use:DataResponseSerializerValidate and preprocess the responseData.
  • responseString(queue:encoding:completionHandler:): Use what is providedString.EncodingThe response isDataResolve toString.
  • responseJSON(queue:options:completionHandler): Use what is providedJSONSerialization.ReadingOptionsuseJSONSerializationParse the responseData. This method is not recommended and is provided only for compatibility with existing Alamofire usage. Instead, useresponseDecodable.
  • responseDecodable(of:queue:decoder:completionHandler:): Use what is providedDataDecoderThe response isDataResolved to provide or inferDecodableType. It is used by defaultJSONDecoder. This approach is recommended for JSON and generic response parsing.

DataResponseSerializer

Call responseData(Queue :completionHandler:) on a DataRequest or DownloadRequest to verify that the Data has been returned correctly using the DataResponseSerializer (unless EmptyResponseMethods and emptyResponseCodes allow, otherwise null responses are not allowed) and pass this Data to the dataPreprocessor. This response handler is useful for custom Data processing, but is usually not required.

StringResponseSerializer

Calls to DataRequest or DownloadRequest responseString (queue: encoding: completionHa StringResponseSerializer authentication Data Is returned correctly (null responses are not allowed unless emptyResponseMethods and emptyResponseCodes allow it) and passes this Data to the dataPreprocessor. The Data is then processed using string. Encoding parsed from HTTPURLResponse and a String is initialized.

JSONResponseSerializer

Call on DataRequest or DownloadRequest responseJSON (queue: options: completionHandler) using JSONResponseSerializer validation Data Is returned correctly (null responses are not allowed unless emptyResponseMethods and emptyResponseCodes allow it) and passes this Data to the dataPreprocessor. Then, using the provided options for the pretreatment of the Data is passed to the JSONSerialization. JsonObject (with: options:). This serializer is no longer recommended. Instead, use DecodableResponseSerializer provides better experience quickly.

DecodableResponseSerializer

Call on DataRequest or DownloadRequest responseDecodable (of: queue: decoder: completionHandler) using DecodableResponseSerializer To verify that the Data has been returned correctly (null responses are not allowed unless emptyResponseMethods and emptyResponseCodes allow it), and pass that Data to the dataPreprocessor. The preprocessed Data is then passed to the supplied DataDecoder and resolved to the supplied or inferred Decodable type.

Custom response Handlers

In addition to the flexible ResponseSerializer included with Alamofire, there are other ways to customize response handling.

In response to convert

Using the existing ResponseSerializer and then transforming the output is one of the easiest ways to customize the response handler. DataResponse and DownloadResponse both have map, tryMap, mapError, and tryMapError methods that transform the response while retaining the metadata associated with the response. For example, map can be used to extract attributes from a decodable response while preserving any previous parsing errors.

AF.request(.).responseDecodable(of: SomeType.self) { response in
    let propertyResponse = response.map { $0.someProperty }

    debugPrint(propertyResponse)
}
Copy the code

Error-raising conversions can also be used with tryMap, possibly to perform validation:

AF.request(.).responseDecodable(of: SomeType.self) { response in
    let propertyResponse = response.tryMap { try $0.someProperty.validated() }

    debugPrint(propertyResponse)
}
Copy the code

Create a custom response serializer

Creating ResponseSerializer is a good way to encapsulate this logic when the ResponseSerializer provided by Alamofire or the response transformation is not flexible enough, or there is a lot of customization. Integrating custom ResponseSerializer usually has two parts: creating protocol-compliant types and extending related request types for easy use. For example, if the server returns a specially encoded String (perhaps a comma-separated value), the ResponseSerializer in this format might look something like this:

struct CommaDelimitedSerializer: ResponseSerializer {
    func serialize(
        request: URLRequest? .response: HTTPURLResponse? .data: Data? .error: Error?). throws- > [String] {
        // Call the existing StringResponseSerializer to get many behaviors automatically.
        let string = try StringResponseSerializer().serialize(
            request: request,
            response: response,
            data: data,
            error: error
        )

        return Array(string.split(separator: ","))}}Copy the code

Note that the return type of the serialize method meets the SerializedObject AssociatedType requirement. In more complex serializer, the return type itself can be generic, thereby allowing the generic type of serialization, as shown in DecodableResponseSerializer.

To make the CommaDelimitedSerializer more useful, you can add other behaviors, such as allowing empty HTTP methods and response code to be customized by passing them to the underlying StringResponseSerializer.

Network accessibility

NetworkReachabilityManager listening in mobile network and WiFi network interface of the host and accessibility change of address.

let manager = NetworkReachabilityManager(host: "www.apple.com")

manager?.startListening { status in
    print("Network Status Changed: \(status)")}Copy the code

Be sure to remember to save the Manager or state changes will not be reported. Also, do not include Scheme in the host string, or reachability will not work properly.

There are some important things to keep in mind when using network accessibility to determine what to do next.

  • Don’tUse reachability to determine whether a network request should be sent.
    • You should always send out requests.
  • After accessibility is restored, retry failed network requests using events.
    • Although the network request may still fail, now is a good time to retry the request.
  • Network reachability status can be used to determine the cause of network request failure.
    • If a network request fails, it is more useful to tell the user that the network request failed because it was offline, rather than a more technical error such as “Request timed out.”

Alternatively, it may be simpler and more reliable to use a RequestRetrier, such as the built-in RetryPolicy, rather than using accessibility updates to retry requests that fail due to network failures. By default, RetryPolicy will retry idempotent requests under various error conditions, including offline network connections.