Session
Session InstancesSession With a URLSessionConfigurationSessionDelegatestartRequestsImmediatelySession’s DispatchQueuesRequestInterceptorServerTrustManagerRedirectHandlerCachedResponseHandlerEventMonitorsURLSessionsRequestRequest’s URLRequestsURLSessionTasksURLSessionTaskMetricsDataRequestDataStreamRequestUploadRequestDownloadRequestRequestInterceptor
EventMonitors
Alamofire is built on top of URLSession and the Foundation URL Loading System. To make the most of this framework, it is recommended that you be familiar with the concepts and capabilities of the underlying networking stack.
Recommended Reading
URLSession Class ReferenceURLCache Class ReferenceURLAuthenticationChallenge Class ReferenceSessionAlamofire’s Session is roughly equivalent in responsibility to the URLSession instance it maintains: it provides API to produce the various Request subclasses encapsulating different URLSessionTask subclasses, as well as encapsulating a variety of configuration applied to all Requests produced by the instance.
Session provides a default singleton instance which powers the top-level API from the AF enum namespace. As such, the following two statements are equivalent:
AF.request("https://httpbin.org/get")
let session = Session.default
session.request("https://httpbin.org/get")
Session InstancesMost applications will need to customize the behavior of their Session instances in a variety of ways. The easiest way to accomplish this is to use the following convenience initializer and store the result in a singleton used throughout the app.
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] = [])
This initializer allows the customization of all fundamental Session behaviors.
Session With a URLSessionConfigurationTo customize the behavior of the underlying URLSession, a customized URLSessionConfiguration instance can be provided. Starting from the URLSessionConfiguration.af.default instance is recommended, as it adds the default Accept-Encoding, Accept-Language, and User-Agent headers provided by Alamofire, but any URLSessionConfiguration can be used.
let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false
let session = Session(configuration: configuration)
URLSessionConfigurationis not the recommended location to setAuthorizationorContent-Typeheaders. Instead, add them toRequests using the providedheadersAPIs, usingParameterEncoders, or aRequestAdapter.As Apple states in their documentation, mutating
URLSessionConfigurationproperties after the instance has been added to aURLSession(or, in Alamofire’s case, used to initialize aSession) has no effect.
SessionDelegateA SessionDelegate instance encapsulates all handling of the various URLSessionDelegate and related protocols callbacks. SessionDelegate also acts as the SessionStateDelegate for every Request produced by Alamofire, allowing the Request to indirectly import state from the Session instance that created them. SessionDelegate can be customized with a specific FileManager instance, which will be used for any disk access, like accessing files to be uploaded by UploadRequests or files downloaded by DownloadRequests.
let delelgate = SessionDelegate(fileManager: .default)
startRequestsImmediatelyBy default, Session will call resume() on a Request as soon as it has added at least one response handler. Setting startRequestsImmediately to false requires that all Requests have resume() called manually.
let session = Session(startRequestsImmediately: false)
Session’s DispatchQueuesBy default, Session instances use a single DispatchQueue for all asynchronous work. This includes the underlyingQueue of the URLSession’s delegate OperationQueue, for all URLRequest creation, all response serialization work, and all internal Session and Request state mutation. If performance analysis shows a particular bottleneck around URLRequest creation or response serialization, Session can be provided with separate DispatchQueues for each area of work.
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)
Any custom rootQueue provided MUST be a serial queue, but requestQueue and serializationQueue can be either serial or parallel queues. Serial queues are the recommended default unless performance analysis shows work being delayed, in which case making the queues parallel may help overall performance.
RequestInterceptorAlamofire’s RequestInterceptor protocol (RequestAdapter & RequestRetrier) provides important and powerful request adaptation and retry features. It can be applied at both the Session and Request level. For more details on RequestInterceptor and the various implementations Alamofire includes, like RetryPolicy, see below.
let policy = RetryPolicy()
let session = Session(interceptor: policy)
ServerTrustManagerAlamofire’s ServerTrustManager class encapsulates mappings between domains and instances of ServerTrustEvaluating-conforming types, which provide the ability to customize a Session’s handling of TLS security. This includes the use of certificate and public key pinning as well as certificate revocation checking. For more information, see the section about the ServerTrustManager and ServerTrustEvaluating. Initializing a ServerTrustManger is as simple as providing a mapping between the domain and the type of evaluation to be performed:
let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let session = Session(serverTrustManager: manager)
For more details on evaluating server trusts, see the detailed documentation below.
RedirectHandlerAlamofire’s RedirectHandler protocol customizes the handling of HTTP redirect responses. It can be applied at both the Session and Request level. Alamofire includes the Redirector type which conforms to RedirectHandler and offers simple control over redirects. For more details on RedirectHandler, see the detailed documentation below.
let redirector = Redirector(behavior: .follow)
let session = Session(redirectHandler: redirector)
CachedResponseHandlerAlamofire’s CachedResponseHandler protocol customizes the caching of responses and can be applied at both the Session and Request level. Alamofire includes the ResponseCacher type which conforms to CachedResponseHandler and offers simple control over response caching. For more details, see the detailed documentation below.
let cacher = ResponseCacher(behavior: .cache)
let session = Session(cachedResponseHandler: cacher)
EventMonitorsAlamofire’s EventMonitor protocol provides powerful insight into Alamofire’s internal events. It can be used to provide logging and other event-based features. Session accepts an array of EventMonitor-conforming instances at initialization time.
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
URLSessionsIn addition to the convenience initializer mentioned previously, Sessions can be initialized directly from URLSessions. However, there are several requirements to keep in mind when using this initializer, so using the convenience initializer is recommended. These include:
URLSessions configured for background use. This will lead to a runtime error when the Session is initialized.SessionDelegate instance must be created and used as the URLSession’s delegate, as well as passed to the Session initializer.A custom OperationQueue must be passed as the URLSession’s delegateQueue. This queue must be a serial queue, it must have a backing DispatchQueue, and that DispatchQueue must be passed to the Session as its rootQueue.
let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = rootQueue
let delegate = SessionDelegate()
let configuration = URLSessionConfiguration.af.default
let urlSession = URLSession(configuration: configuration,
delegate: delegate,
delegateQueue: queue)
let session = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)
Each request performed by Alamofire is encapsulated by particular class, DataRequest, UploadRequest, and DownloadRequest. Each of these classes encapsulate functionality unique to each type of request, but DataRequest and DownloadRequest inherit from a common superclass, Request (UploadRequest inherits from DataRequest). Request instances are never created directly, but are instead vended from a Session instance through one of the various request methods.
Once a Request subclass has been created with it’s initial parameters or URLRequestConvertible value, it is passed through the series of steps making up Alamofire’s request pipeline. For a successful request, these include:
URLRequestConvertible value. If a URLRequestConvertible value is passed directly, that value is used unchanged.asURLRequest() is called on the the URLRequestConvertible value, creating the first URLRequest value. This value is passed to the Request and stored in requests. If the URLRequestConvertible value was created from the parameters passed to a Session method, any provided RequestModifier is called when the URLRequest is created.Session or Request RequestAdapters or RequestInterceptors, they’re called using the previously created URLRequest. The adapted URLRequest is then passed to the Request and stored in requests as well.Session calls the Request to create the URLSessionTask to perform the network request based on the URLRequest.URLSessionTask is complete and URLSessionTaskMetrics have been gathered, the Request executes its Validators.responseDecodable, that have been appended.At any one of these steps, a failure can be indicated through a created or received Error value, which is then passed to the associated Request. For example, aside from steps 1 and 4, all of the steps above can create an Error which is then passed to the response handlers or available for retry. Here are a few examples of what can or cannot fail throughout the Request pipeline.
URLRequestConvertible value can create an error when asURLRequest() is called. This allows for the initial validation of various URLRequest properties or the failure of parameter encoding.RequestAdapters can fail during adaptation, perhaps due to a missing authorization token.URLSessionTask creation cannot fail.URLSessionTasks can complete with errors for a variety of reasons, including network availability and cancellation. These Error values are passed back to the Request.Error, usually due to an invalid response or other parsing error.Once an error is passed to the Request, the Request will attempt to run any RequestRetriers associated with the Session or Request. If any RequestRetriers choose to retry the Request, the complete pipeline is run again. RequestRetriers can also produce Errors, which do not trigger retry.
RequestAlthough Request doesn’t encapsulate any particular type of request, it contains the state and functionality common to all requests Alamofire performs. This includes:
All Request types include the notion of state, indicating the major events in the Request’s lifetime.
public enum State {
case initialized
case resumed
case suspended
case cancelled
case finished
}
Requests start in the .initialized state after their creation. Requests can be suspended, resumed, and cancelled by calling the appropriate lifetime method.
resume() resumes, or starts, a Request’s network traffic. If startRequestsImmediately is true, this is called automatically once a response handler has been added to the Request.suspend() suspends, or pauses the Request and its network traffic. Requests in this state can be resumed, but only DownloadRequests may be able continue transferring data. Other Requests will start over.cancel() cancels a Request. Once in this state, a Request cannot be resumed or suspended. When cancel() is called, the Request’s error property will be set with an AFError.explicitlyCancelled instance.
If a Request is resumed and isn’t later cancelled, it will reach the .finished state once all response validators and response serializers have been run. However, if additional response serializers are added to the Request after it has reached the .finished state, it will transition back to the .resumed state and perform the network request again.In order to track the progress of a request, Request offers a both uploadProgress and downloadProgress properties as well as closure-based uploadProgress and downloadProgress methods. Like all closure-based Request APIs, the progress APIs can be chained off of the Request with other methods. Also like the other closure-based APIs, they should be added to a request before adding any response handlers, like responseDecodable.
AF.request(...)
.uploadProgress { progress in
print(progress)
}
.downloadProgress { progress in
print(progress)
}
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
Importantly, not all Request subclasses are able to report their progress accurately, or may have other dependencies to do so.
Data object provided as the upload body to an UploadRequest.UploadRequest.Content-Length header on the request, if it has been manually set.Content-Length header.
Unfortunately there may be other, undocumented requirements for progress reporting from URLSession which prevents accurate progress reporting.Alamofire’s RedirectHandler protocol provides control and customization of redirect handling for Request. In addition to per-Session RedirectHandlers, each Request can be given its own RedirectHandler which overrides any provided by the Session.
let redirector = Redirector(behavior: .follow)
AF.request(...)
.redirect(using: redirector)
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
Note: Only one
RedirectHandlercan be set on aRequest. Attempting to set more than one will result in a runtime exception.
Alamofire’s CachedResponseHandler protocol provides control and customization over the caching of responses. In addition to per-Session CachedResponseHandlers, each Request can be given its own CachedResponseHandler which overrides any provided by the Session.
let cacher = ResponseCacher(behavior: .cache)
AF.request(...)
.cacheResponse(using: cacher)
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
Note: Only one
CachedResponseHandlercan be set on aRequest. Attempting to set more than one will result in a runtime exception.
In order to take advantage of the automatic credential handling provided by URLSession, Alamofire provides per-Request API to allow the automatic addition of URLCredential instances to requests. These include both convenience API for HTTP authentication using a username and password, as well as any URLCredential instance.
Adding a credential to automatically reply to any HTTP authentication challenge is straightforward:
AF.request(...)
.authenticate(username: "user@example.domain", password: "password")
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
Note: This mechanism only supports HTTP authentication prompts. If a request requires an
Authenticationheader for all requests, it should be provided directly, either as part of theRequest, or through aRequestInterceptor.
Additionally, adding a raw URLCredential is just as easy:
let credential = URLCredential(...)
AF.request(...)
.authenticate(using: credential)
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
Request’s URLRequestsEach network request issued by a Request is ultimately encapsulated in a URLRequest value created from the various parameters passed to one of the Session request methods. Request will keep a copy of these URLRequests in its requests array property. These values include both the initial URLRequest created from the passed parameters, as well any URLRequests created by RequestInterceptors. That array does not, however, include the URLRequests performed by the URLSessionTasks issued on behalf of the Request. To inspect those values, the tasks property gives access to all of the URLSessionTasks performed by the Request.
URLSessionTasksIn many ways, the various Request subclasses act as a wrapper for a URLSessionTask, presenting particular API for interacting with particular types of tasks. These tasks are made visible on the Request instance through the tasks array property. This includes both the initial task created for the Request, as well as any subsequent tasks created as part of the retry process, with one task per retry.
Each Request may have an HTTPURLResponse value available once the request is complete. This value is only available if the request wasn’t cancelled and didn’t fail to make the network request. Additionally, if the request is retried, only the last response is available. Intermediate responses can be derived from the URLSessionTasks in the tasks property.
URLSessionTaskMetricsAlamofire gathers URLSessionTaskMetrics values for every URLSessionTask performed for a Request. These values are available in the metrics property, with each value corresponding to the URLSessionTask in tasks at the same index.
URLSessionTaskMetrics are also made available on Alamofire’s various response types, like DataResponse. For instance:
AF.request(...)
.responseDecodable(of: SomeType.self) { response in {
print(response.metrics)
}
Due to
FB7624529, collection ofURLSessionTaskMetricson watchOS is currently disabled.
DataRequestDataRequest is a subclass of Request which encapsulates a URLSessionDataTask downloading a server response into Data stored in memory. Therefore, it’s important to realize that extremely large downloads may adversely affect system performance. For those types of downloads, using DownloadRequest to save the data to disk is recommended.
DataRequests have a few properties in addition to those provided by Request. These include data, which is the accumulated Data from the server response, and convertible, which is the URLRequestConvertible the DataRequest was created with, containing the original parameters creating the instance.
DataRequests do not validate responses by default. Instead, a call to validate() must be added to the request in order to verify various properties are valid.
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> Result<Void, Error>
By default, adding validate() ensures the response status code is within the 200..<300 range and that the response’s Content-Type matches the request's Accept value. Validation can be further customized by passing a Validation closure:
AF.request(...)
.validate { request, response, data in
...
}
DataStreamRequestDataStreamRequest is a subclass of Request which encapsulates a URLSessionDataTask and streams Data from an HTTP connection over time.
DataStreamRequest contains no additional public state.
DataStreamRequests do not validate responses by default. Instead, a call to validate() must be added to the request in order to verify various properties are valid.
public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> Result<Void, Error>
By default, adding validate() ensures the response status code is within the 200..<300 range and that the response’s Content-Type matches the request's Accept value. Validation can be further customized by passing a Validation closure:
AF.request(...)
.validate { request, response in
...
}
UploadRequestUploadRequest is a subclass of DataRequest which encapsulates a URLSessionUploadTask, uploading a Data value, file on disk, or InputStream to a remote server.
UploadRequests have a few properties in addition to those provided by DataRequest. These include a FileManager instance, used to customize access to disk when uploading a file, and upload, which encapsulates both the URLRequestConvertible value used to describe the request, as well as the Uploadable, which determines the type of upload being performed.
DownloadRequestDownloadRequest is a concrete subclass of Request which encapsulates a URLSessionDownloadTask, downloading response Data to disk.
DownloadRequests have a few properties in addition to those provided by Request. These include resumeData, the Data produced when cancelling a DownloadRequest, which may be used to resume the download later, and fileURL, the URL at which the downloaded file is available once the download completes.
In addition to supporting the cancel() method provided by Request, DownloadRequest includes cancel(producingResumeData shouldProduceResumeData: Bool), which optionally populates the resumeData property when cancelled, if possible, and cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void), which provides the produced resume data to the passed closure.
AF.download(...)
.cancel { resumeData in
...
}
DownloadRequest supports a slightly different version of validation than DataRequest and UploadRequest, due to the fact it’s data is downloaded to disk.
public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse, _ fileURL: URL?)
Instead of accessing the downloaded Data directly it must be accessed using the fileURL provided. Otherwise, the capabilities of DownloadRequest’s validators are the same as DataRequest’s.
RequestInterceptorAlamofire’s RequestInterceptor protocol (composed of the RequestAdapter and RequestRetrier protocols) enables powerful per-Session and per-Request capabilities. These include authentication systems, where a common header is added to every Request and Requests are retried when authorization expires. Additionally, Alamofire includes a built in RetryPolicy type, which enables easy retry when requests fail due to a variety of common network errors.
RequestAdapterAlamofire’s RequestAdapter protocol allows each URLRequest that’s to be performed by a Session to be inspected and mutated before being issued over the network. One very common use of an adapter is to add an Authorization header to requests behind a certain type of authentication.
The RequestAdapter protocol has a single requirement:
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
Its parameters include:
urlRequest: The URLRequest initially created from the parameters or URLRequestConvertible value used to create the Request.session: The Session which created the Request for which the adapter is being called.completion: The asynchronous completion handler that must be called to indicate the adapter is finished. It’s asynchronous nature enables RequestAdapters to access asynchronous resources from the network or disk before the Request is sent over the network. The Result provided to the completion closure can either return a .success value with the modified URLRequest value, or a .failure value with an associated Error which will then be used to fail the Request.
For example, adding an Authorization header requires modifying the URLRequest and then calling the completion handler.
let accessToken: String
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.headers.add(.authorization(bearerToken: accessToken))
completion(.success(urlRequest))
}
RequestRetrierAlamofire’s RequestRetrier protocol allows a Request that encountered an Error while being executed to be retried. This includes Errors produced at any stage of Alamofire’s request pipeline.
RequestRetrier has a single requirement.
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
Its parameters include:
request: The Request which encountered an error.session: The Session managing the Request.error: The Error which triggered the retry attempt. Usually an AFError.completion: The asynchronous completion handler that must be called to indicate whether the Request should be retried. It must be called with a RetryResult.The RetryResult type represents the outcome of whatever logic is implemented in the RequestRetrier. It’s 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)
}
For example, Alamofire’s RetryPolicy type will automatically retry Requests that fail due to a network error of some kind, if the request is idempotent.
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)
}
}
Using a secure HTTPS connection when communicating with servers and web services is an important step in securing sensitive data. By default, Alamofire receives the same automatic TLS certificate and certificate chain validation as URLSession. While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities. In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by Alamofire’s ServerTrustEvaluating protocol.
ServerTrustManager and ServerTrustEvaluatingServerTrustEvaluatingThe ServerTrustEvaluating protocol provides a way to perform any sort of server trust evaluation. It has a single requirement:
func evaluate(_ trust: SecTrust, forHost host: String) throws
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 evaluators, providing composable control over the evaluation process:
DefaultTrustEvaluator: Uses the default server trust evaluation while allowing you to control whether to validate the host provided by the challenge.RevocationTrustEvaluator: Checks the status of the received certificate to ensure it hasn’t been revoked. This isn’t usually performed on every request due to the network request overhead it entails.PinnedCertificatesTrustEvaluator: Uses the provided certificates to validate the server trust. The server trust is considered valid if one of the pinned certificates match one of the server certificates. This evaluator can also accept self-signed certificates.PublicKeysTrustEvaluator: Uses the provided public keys to validate the server trust. The server trust is considered valid if one of the pinned public keys match one of the server certificate public keys.CompositeTrustEvaluator: Evaluates an array of ServerTrustEvaluating values, only succeeding if all of them are successful. This type can be used to combine, for example, the RevocationTrustEvaluator and the PinnedCertificatesTrustEvaluator.DisabledEvaluator: This evaluator should only be used in debug scenarios as it disables all evaluation which in turn will always consider any server trust as valid. This evaluator should never be used in production environments!ServerTrustManagerThe ServerTrustManager is responsible for storing an internal mapping of ServerTrustEvaluating values to a particular host. This allows Alamofire to evaluate each host with different evaluators.
let evaluators: [String: ServerTrustEvaluating] = [
// By default, certificates included in the app bundle are pinned automatically.
"cert.example.com": PinnedCertificatesTrustEvalutor(),
// By default, public keys from certificates included in the app bundle are used automatically.
"keys.example.com": PublicKeysTrustEvalutor(),
]
let manager = ServerTrustManager(evaluators: serverTrustPolicies)
This ServerTrustManager will have the following behaviors:
cert.example.com will always use certificate pinning with default and host validation enabled , thus requiring the following criteria to be met in order to allow the TLS handshake to succeed:
keys.example.com will always use public key pinning with default and host validation enabled, thus requiring the following criteria to be met in order to allow the TLS handshake to succeed:
ServerTrustManager requires all hosts to be evaluated by default.If you find yourself needing more flexible server trust policy matching behavior (i.e. wildcard domains), then subclass the 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
}
}
With the addition of App Transport Security (ATS) in iOS 9, it is possible that using a custom ServerTrustManager with several ServerTrustEvaluating objects will have no effect. If you continuously see CFNetwork SSLHandshake failed (-9806) errors, you have probably run into this problem. Apple's ATS system overrides the entire challenge system unless you configure the ATS settings in your app's plist to disable enough of it to allow your app to evaluate the server trust. If you run into this problem (high probability with self-signed certificates), you can work around this issue by adding NSAppTransportSecurity overrides to your Info.plist. You can use the nscurl tool’s --ats-diagnostics option to perform a series of tests against a host to see which ATS overrides might be required.
If you are attempting to connect to a server running on your localhost, and you are using self-signed certificates, you will need to add the following to your Info.plist.
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
According to Apple documentation, setting NSAllowsLocalNetworking to YES allows loading local resources without disabling ATS for the rest of your app.
URLSession allows the customization of caching and redirect behaviors using URLSessionDataDelegate and URLSessionTaskDelegate methods. Alamofire surfaces these customization points as the CachedResponseHandler and RedirectHandler protocols.
CachedResponseHandlerThe CachedResponseHandler protocol allows control over the caching of HTTP responses into the URLCache instance associated with the Session making a request. The protocol has a single requirement:
func dataTask(_ task: URLSessionDataTask,
willCacheResponse response: CachedURLResponse,
completion: @escaping (CachedURLResponse?) -> Void)
As can be seen in the method signature, this control only applies to Requests that use an underlying URLSessionDataTask for network transfers, which include DataRequests and UploadRequests (since URLSessionUploadTask is a subclass of URLSessionDataTask). The conditions under which a response will be considered for caching are extensive, so it’s best to review the documentation of the URLSessionDataDelegate method urlSession(_:dataTask:willCacheResponse:completionHandler:). Once a response is considered for caching, there are variety of valuable manipulations that can be made:
nil CachedURLResponse.CachedURLResponse’s storagePolicy to change where the cached value should live.URLResponse directly, adding or removing values.Data associated with the response, if any.Alamofire includes the ResponseCacher type which conforms to CachedResponseHandler, making it easy to cache, not cache, or modify a response. ResponseCacher takes a Behavior value to control the caching 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?)
}
ResponseCacher can be used on both a Session and Request basis, as outlined above.
RedirectHandlerThe RedirectHandler protocol allows control over the redirect behavior of particular Requests. It has a single requirement:
func task(_ task: URLSessionTask,
willBeRedirectedTo request: URLRequest,
for response: HTTPURLResponse,
completion: @escaping (URLRequest?) -> Void)
This method provides an opportunity to modify the redirected URLRequest or pass nil to disable the redirect entirely. Alamofire provides the Redirectortype which conforms to RedirectHandler, making it easy to follow, not follow, or modify a redirected request. Redirector takes a Behavior value to control the redirect 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?)
}
Redirector can be used on both a Session and Request basis, as outlined above.
EventMonitorsThe EventMonitor protocol allows the observation and inspection of a large number of internal Alamofire events. These include all URLSessionDelegate, URLSessionTaskDelegate, and URLSessionDownloadDelegate methods implemented by Alamofire as well as a large number of internal Request events. In addition to these events, which by default are an empty method that does no work, the EventMonitor protocol also requires a DispatchQueue on which all the events are dispatched in order to maintain performance. This DispatchQueue defaults to .main, but dedicated serial queues are recommended for any custom conforming types.
Perhaps the biggest use of the EventMonitor protocol is to implement the logging of relevant events. A simple implementation may look something 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)")
}
}
This Logger type can be added to a Session in the same way demonstrated above:
let logger = Logger()
let session = Session(eventMonitors: [logger])
As a framework, Alamofire has two main goals:
It accomplishes these goals through the use of powerful abstractions, providing useful defaults, and included implementations of common tasks. However, once use of Alamofire has gone beyond a few requests, it’s necessary to move beyond the high level, default implementations into behavior customized for particular applications. Alamofire provides the URLConvertible and URLRequestConvertible protocols to help with this customization.
URLConvertibleTypes adopting the URLConvertible protocol can be used to construct URLs, which are then used to construct URL requests internally. String, URL, and URLComponents conform to URLConvertible by default, allowing any of them to be passed as url parameters 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)
Applications interacting with web applications in a significant manner are encouraged to have custom types conform to URLConvertible as a convenient way to map domain-specific models to server resources.
URLRequestConvertibleTypes adopting the URLRequestConvertible protocol can be used to construct URLRequests. URLRequest conforms to URLRequestConvertible by default, allowing it to be passed into request, upload, and download methods directly. Alamofire uses URLRequestConvertible as the foundation of all requests flowing through the request pipeline. Using URLRequests directly the recommended way to customize URLRequest creation outside of the ParameterEncoders that Alamofire provides.
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)
Applications interacting with web applications in a significant manner are encouraged to have custom types conform to URLRequestConvertible as a way to ensure consistency of requested endpoints. Such an approach can be used to abstract away server-side inconsistencies and provide type-safe routing, as well as manage other state.
As apps grow in size, it's important to adopt common patterns as you build out your network stack. An important part of that design is how to route your requests. The Alamofire URLConvertible and URLRequestConvertible protocols along with the Router design pattern are here to help.
A “router” is a type that defines “routes”, or the components of a request. These components can include the parts of a URLRequest, the parameters required to make a request, as well as various per-request Alamofire settings. A simple router could look something 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() throws -> URLRequest {
let url = baseURL.appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
return request
}
}
AF.request(Router.get)
More complex routers may include the parameters of a request. With Alamofire’s ParameterEncoder protocol and included encoders, any Encodable type can be used as parameters:
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() 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
}
}
Routers can be expanded for any number of endpoints with any number of configurable properties, but once a certain level of complexity has been reached, separating one big router into smaller routers for parts of an API should be considered.
Alamofire provides response handling through various response methods and the ResponseSerializer protocol.
Both DataRequest and DownloadRequest offer methods that allow response handling without invoking any ResponseSerializer at all. This is most important for DownloadRequests where loading large files into memory may not be possible.
// DataRequest
func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDataResponse<Data?>) -> Void) -> Self
// DownloadRequest
func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDownloadResponse<URL?>) -> Void) -> Self
As with all response handlers, all serialization work (in this case none) is performed on an internal queue and the completion handler called on the queue passed to the method. This means that it’s not necessary to dispatch back to the main queue by default. However, if there is to be any significant work performed in the completion handler, passing a custom queue to the response methods is recommended, with a dispatch back to main in the handler itself if necessary.
ResponseSerializerThe ResponseSerializer protocol is composed of the DataResponseSerializerProtocol and DownloadResponseSerializerProtocol protocols. The combined version of ResponseSerializer looks like this:
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
}
By default, the serializeDownload method is implemented by reading the downloaded Data from disk and calling serialize with it. Therefore, it may be more appropriate to implement custom handling for large downloads using DownloadRequest’s response(queue:completionHandler:) method mentioned above.
ResponseSerializer provides various default implementations for the dataPreprocessor, emptyResponseMethods, and emptyResponseCodes which can be customized in conforming types, like various ResponseSerializers included with Alamofire.
All ResponseSerializer usage flows through methods on DataRequest and DownloadRequest:
// 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
Alamofire includes a few common responses handlers, including:
responseData(queue:completionHandler): Validates and preprocesses the response Data using DataResponseSerializer.responseString(queue:encoding:completionHandler:): Parses the response Data as a String using the provided String.Encoding.responseJSON(queue:options:completionHandler): Parses the response Data using JSONSerialization using the provided JSONSerialization.ReadingOptions. Using this method is not recommended and is only offered for compatibility with existing Alamofire usage. Instead, responseDecodable should be used.responseDecodable(of:queue:decoder:completionHandler:): Parses the response Data into the provided or inferred Decodable type using the provided DataDecoder. Uses JSONDecoder by default. Recommend method for JSON and generic response parsing.DataResponseSerializerCalling responseData(queue:completionHandler:) on DataRequest or DownloadRequest uses a DataResponseSerializer to validate that Data has been returned appropriately (no empty responses unless allowed by the emptyResponseMethods and emptyResponseCodes) and passes that Data through the dataPreprocessor. This response handler is useful for customized Data handling but isn’t usually necessary.
StringResponseSerializerCalling responseString(queue:encoding:completionHandler) on DataRequest or DownloadRequest uses a StringResponseSerializer to validate that Data has been returned appropriately (no empty responses unless allowed by the emptyResponseMethods and emptyResponseCodes) and passes that Data through the dataPreprocessor. The preprocessed Data is then used to initialize a String using the String.Encoding parsed from the HTTPURLResponse.
JSONResponseSerializerCalling responseJSON(queue:options:completionHandler) on DataRequest or DownloadRequest uses a JSONResponseSerializer to validate that Data has been returned appropriately (no empty responses unless allowed by the emptyResponseMethods and emptyResponseCodes) and passes that Data through the dataPreprocessor. The preprocessed Data is then passed through JSONSerialization.jsonObject(with:options:) with the provided options. This serializer is no longer recommended. Instead, using the DecodableResponseSerializer provides a better Swift experience.
DecodableResponseSerializerCalling responseDecodable(of:queue:decoder:completionHandler) on DataRequest or DownloadRequest uses a DecodableResponseSerializerto validate that Data has been returned appropriately (no empty responses unless allowed by the emptyResponseMethods and emptyResponseCodes) and passes that Data through the dataPreprocessor. The preprocessed Data is then passed through the provided DataDecoder and parsed into the provided or inferred Decodable type.
In addition to the flexible ResponseSerializers included with Alamofire, there are additional ways to customize response handling.
Using an existing ResponseSerializer and then transforming the output is one of the simplest ways of customizing response handlers. Both DataResponse and DownloadResponse have map, tryMap, mapError, and tryMapError methods that can transform responses while preserving the metadata associated with the response. For example, extracting a property from a Decodable response can be achieved using map, while also preserving any previous parsing errors.
AF.request(...).responseDecodable(of: SomeType.self) { response in
let propertyResponse = response.map { $0.someProperty }
debugPrint(propertyResponse)
}
Transforms that throw errors can also be used with tryMap, perhaps to perform validation:
AF.request(..).responseDecodable(of: SomeType.self) { response in
let propertyResponse = response.tryMap { try $0.someProperty.validated() }
debugPrint(propertyResponse)
}
When Alamofire’s provided ResponseSerializers or response transforms aren’t flexible enough, or the amount of customization is extensive, creating a ResponseSerializer is a good way to encapsulate that logic. There are usually two parts to integrating a custom ResponseSerializer: creating the conforming type and extending the relevant Request type(s) to make it convenient to use. For example, if a server returned a specially encoded String, perhaps values separated by commas, the ResponseSerializer for such a format could 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: ","))
}
}
Note that the SerializedObject associatedtype requirement is met by the return type of the serialize method. In more complex serializers, this return type itself can be generic, allowing the serialization of generic types, as seen by the DecodableResponseSerializer.
To make the CommaDelimitedSerializer more useful, additional behaviors could be added, like allowing the customization of empty HTTP methods and response codes by passing them through to the underlying StringResponseSerializer.
DataStreamRequest uses its own unique response handler type to process incoming Data as part of a stream. In addition to the provided handlers, custom serialization can be performed through the use of the DataStreamSerializer protocol.
public protocol DataStreamSerializer {
/// Type produced from the serialized `Data`.
associatedtype SerializedObject
/// Serializes incoming `Data` into a `SerializedObject` value.
///
/// - Parameter data: `Data` to be serialized.
///
/// - Throws: Any error produced during serialization.
func serialize(_ data: Data) throws -> SerializedObject
}
Any custom DataStreamSerializer can be used to process streaming Data by using the responseStream method:
AF.streamRequest(...).responseStream(using: CustomSerializer()) { stream in
// Process stream.
}
Alamofire includes DecodableStreamSerializer, a DataStreamSerializer which can parse Decodable types from incoming Data. It can be customized with both a DataDecoder instance and a DataPreprocessor and used through the responseStreamDecodable method:
AF.streamRequest(...).responseDecodable(of: DecodableType.self) { stream in
// Process stream.
}
Or by using it directly in the previously mentioned streamResponse method:
AF.streamRequest(...).responseStream(using: DecodableStreamSerializer<DecodableType>(decoder: JSONDecoder())) { stream in
// Process stream.
}
The NetworkReachabilityManager listens for changes in the reachability of hosts and addresses for both Cellular and WiFi network interfaces.
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.startListening { status in
print("Network Status Changed: \(status)")
}
Make sure to remember to retain the
managerin the above example, or no status changes will be reported. Also, do not include the scheme in thehoststring or reachability won't function correctly.
There are some important things to remember when using network reachability to determine what to do next.
Alternatively, using a RequestRetrier, like the built in RetryPolicy, instead of reachability updates to retry requests which failed to a network failure will likely be simpler and more reliable. By default, RetryPolicy will retry idempotent requests on a variety of error conditions, including an offline network connection.