Browse Source

Update advanced usage request retry/adapt documention for Alamofire 5 (#3033)

Tim 6 years ago
parent
commit
247e8ba2c6
1 changed files with 42 additions and 45 deletions
  1. 42 45
      Documentation/AdvancedUsage.md

+ 42 - 45
Documentation/AdvancedUsage.md

@@ -9,9 +9,9 @@ Alamofire is built on `URLSession` and the Foundation URL Loading System. To mak
 - [URLCache Class Reference](https://developer.apple.com/reference/foundation/urlcache)
 - [URLAuthenticationChallenge Class Reference](https://developer.apple.com/reference/foundation/urlauthenticationchallenge)
 
-### Session Manager
+### Session
 
-Top-level convenience methods like `Alamofire.request` use a default instance of `Alamofire.SessionManager`, which is configured with the default `URLSessionConfiguration`.
+Top-level convenience methods like `Alamofire.request` use a default instance of `Alamofire.Session`, which is configured with the default `URLSessionConfiguration`.
 
 As such, the following two statements are equivalent:
 
@@ -20,50 +20,50 @@ Alamofire.request("https://httpbin.org/get")
 ```
 
 ```swift
-let sessionManager = Alamofire.SessionManager.default
-sessionManager.request("https://httpbin.org/get")
+let session = Alamofire.Session.default
+session.request("https://httpbin.org/get")
 ```
 
-Applications can create session managers for background and ephemeral sessions, as well as new managers that customize the default session configuration, such as for default headers (`httpAdditionalHeaders`) or timeout interval (`timeoutIntervalForRequest`).
+Applications can create sessions for background and ephemeral sessions, as well as new sessions that customize the default session configuration, such as for default headers (`httpAdditionalHeaders`) or timeout interval (`timeoutIntervalForRequest`).
 
-#### Creating a Session Manager with Default Configuration
+#### Creating a Session with Default Configuration
 
 ```swift
 let configuration = URLSessionConfiguration.default
-let sessionManager = Alamofire.SessionManager(configuration: configuration)
+let session = Alamofire.Session(configuration: configuration)
 ```
 
-#### Creating a Session Manager with Background Configuration
+#### Creating a Session with Background Configuration
 
 ```swift
 let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
-let sessionManager = Alamofire.SessionManager(configuration: configuration)
+let session = Alamofire.Session(configuration: configuration)
 ```
 
-#### Creating a Session Manager with Ephemeral Configuration
+#### Creating a Session with Ephemeral Configuration
 
 ```swift
 let configuration = URLSessionConfiguration.ephemeral
-let sessionManager = Alamofire.SessionManager(configuration: configuration)
+let session = Alamofire.Session(configuration: configuration)
 ```
 
 #### Modifying the Session Configuration
 
 ```swift
-var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
+var defaultHeaders = Alamofire.Session.defaultHTTPHeaders
 defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"
 
 let configuration = URLSessionConfiguration.default
 configuration.httpAdditionalHeaders = defaultHeaders
 
-let sessionManager = Alamofire.SessionManager(configuration: configuration)
+let session = Alamofire.Session(configuration: configuration)
 ```
 
 > This is **not** recommended for `Authorization` or `Content-Type` headers. Instead, use the `headers` parameter in the top-level `Alamofire.request` APIs, `URLRequestConvertible` and `ParameterEncoding`, respectively.
 
 ### Session Delegate
 
-By default, an Alamofire `SessionManager` instance creates a `SessionDelegate` object to handle all the various types of delegate callbacks that are generated by the underlying `URLSession`. The implementations of each delegate method handle the most common use cases for these types of calls abstracting the complexity away from the top-level APIs. However, advanced users may find the need to override the default functionality for various reasons.
+By default, an Alamofire `Session` instance creates a `SessionDelegate` object to handle all the various types of delegate callbacks that are generated by the underlying `URLSession`. The implementations of each delegate method handle the most common use cases for these types of calls abstracting the complexity away from the top-level APIs. However, advanced users may find the need to override the default functionality for various reasons.
 
 #### Override Closures
 
@@ -86,8 +86,8 @@ open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLR
 The following is a short example of how to use the `taskWillPerformHTTPRedirection` to avoid following redirects to any `apple.com` domains.
 
 ```swift
-let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
-let delegate: Alamofire.SessionDelegate = sessionManager.delegate
+let session = Alamofire.Session(configuration: URLSessionConfiguration.default)
+let delegate: Alamofire.SessionDelegate = session.delegate
 
 delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
     var finalRequest = request
@@ -136,14 +136,14 @@ Generally speaking, either the default implementation or the override closures s
 
 ### Request
 
-The result of a `request`, `download`, `upload` or `stream` methods are a `DataRequest`, `DownloadRequest`, `UploadRequest` and `StreamRequest` which all inherit from `Request`. All `Request` instances are always created by an owning session manager, and never initialized directly.
+The result of a `request`, `download`, `upload` or `stream` methods are a `DataRequest`, `DownloadRequest`, `UploadRequest` and `StreamRequest` which all inherit from `Request`. All `Request` instances are always created by an owning session, and never initialized directly.
 
 Each subclass has specialized methods such as `authenticate`, `validate`, `responseJSON` and `uploadProgress` that each return the caller instance in order to facilitate method chaining.
 
 Requests can be suspended, resumed and cancelled:
 
 - `suspend()`: Suspends the underlying task and dispatch queue.
-- `resume()`: Resumes the underlying task and dispatch queue. If the owning manager does not have `startRequestsImmediately` set to `true`, the request must call `resume()` in order to start.
+- `resume()`: Resumes the underlying task and dispatch queue. If the owning session does not have `startRequestsImmediately` set to `true`, the request must call `resume()` in order to start.
 - `cancel()`: Cancels the underlying task, producing an error that is passed to any registered response handlers.
 
 ### Routing Requests
@@ -311,37 +311,38 @@ Alamofire.request(Router.readUser("mattt")) // GET https://example.com/users/mat
 
 Most web services these days are behind some sort of authentication system. One of the more common ones today is OAuth. This generally involves generating an access token authorizing your application or user to call the various supported web services. While creating these initial access tokens can be laborsome, it can be even more complicated when your access token expires and you need to fetch a new one. There are many thread-safety issues that need to be considered.
 
-The `RequestAdapter` and `RequestRetrier` protocols were created to make it much easier to create a thread-safe authentication system for a specific set of web services.
+The `RequestAdapter` and `RequestRetrier` protocols were created to make it much easier to create a thread-safe authentication system for a specific set of web services. Note that to use either a `RequestAdapter` instance or `RequestRetrier` instance with the `Session`, the instance must conform to `RequestInterceptor`, which is a composite of `RequestAdapter` and `RequestRetrier`. Alamofire provides default implementations in protocol extensions so you only need to change the adapt or retry function as needed.
 
 #### RequestAdapter
 
-The `RequestAdapter` protocol allows each `Request` made on a `SessionManager` to be inspected and adapted before being created. One very specific way to use an adapter is to append an `Authorization` header to requests behind a certain type of authentication.
+The `RequestAdapter` protocol allows each `Request` made on a `Session` to be inspected and adapted before being created. One very specific way to use an adapter is to append an `Authorization` header to requests behind a certain type of authentication.
 
 ```swift
-class AccessTokenAdapter: RequestAdapter {
+class AccessTokenAdapter: RequestInterceptor {
     private let accessToken: String
 
     init(accessToken: String) {
         self.accessToken = accessToken
     }
 
-    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
+    // MARK: - RequestAdapter
+    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
         var urlRequest = urlRequest
 
         if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://httpbin.org") {
             urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
         }
 
-        return urlRequest
+       completion(.success(urlRequest))
     }
 }
 ```
 
 ```swift
-let sessionManager = SessionManager()
-sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")
+let accessTokenManager = AccessTokenAdapter(accessToken: "1234")
+let session = Session(interceptor: accessTokenManager)
 
-sessionManager.request("https://httpbin.org/get")
+session.request("https://httpbin.org/get")
 ```
 
 #### RequestRetrier
@@ -353,14 +354,12 @@ The `RequestRetrier` protocol allows a `Request` that encountered an `Error` whi
 > To reiterate, **do NOT copy** this sample code and drop it into a production application. This is merely an example. Each authentication system must be tailored to a particular platform and authentication type.
 
 ```swift
-class OAuth2Handler: RequestAdapter, RequestRetrier {
+class OAuth2Handler: RequestInterceptor {
     private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void
 
-    private let sessionManager: SessionManager = {
+    private let session: Session = {
         let configuration = URLSessionConfiguration.default
-        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
-
-        return SessionManager(configuration: configuration)
+        return Session(configuration: configuration)
     }()
 
     private let lock = NSLock()
@@ -384,19 +383,19 @@ class OAuth2Handler: RequestAdapter, RequestRetrier {
 
     // MARK: - RequestAdapter
 
-    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
+    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
         if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
             var urlRequest = urlRequest
             urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
             return urlRequest
         }
 
-        return urlRequest
+        completion(.success(urlRequest))
     }
 
     // MARK: - RequestRetrier
 
-    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
+    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
         lock.lock() ; defer { lock.unlock() }
 
         if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
@@ -413,12 +412,12 @@ class OAuth2Handler: RequestAdapter, RequestRetrier {
                         strongSelf.refreshToken = refreshToken
                     }
 
-                    strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
+                    strongSelf.requestsToRetry.forEach { $0(.retry) }
                     strongSelf.requestsToRetry.removeAll()
                 }
             }
         } else {
-            completion(false, 0.0)
+            completion(.doNotRetry)
         }
     }
 
@@ -438,7 +437,7 @@ class OAuth2Handler: RequestAdapter, RequestRetrier {
             "grant_type": "refresh_token"
         ]
 
-        sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
+        session.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
             .responseJSON { [weak self] response in
                 guard let strongSelf = self else { return }
 
@@ -468,24 +467,22 @@ let oauthHandler = OAuth2Handler(
     refreshToken: "ef56789a"
 )
 
-let sessionManager = SessionManager()
-sessionManager.adapter = oauthHandler
-sessionManager.retrier = oauthHandler
+let session = Session(interceptor: oauth2Handler)
 
 let urlString = "\(baseURLString)/some/endpoint"
 
-sessionManager.request(urlString).validate().responseJSON { response in
+session.request(urlString).validate().responseJSON { response in
     debugPrint(response)
 }
 ```
 
-Once the `OAuth2Handler` is applied as both the `adapter` and `retrier` for the `SessionManager`, it will handle an invalid access token error by automatically refreshing the access token and retrying all failed requests in the same order they failed.
+Once the `OAuth2Handler` is applied as both the `adapter` and `retrier` for the `Session`, it will handle an invalid access token error by automatically refreshing the access token and retrying all failed requests in the same order they failed.
 
 > If you needed them to execute in the same order they were created, you could sort them by their task identifiers.
 
 The example above only checks for a `401` response code which is not nearly robust enough, but does demonstrate how one could check for an invalid access token error. In a production application, one would want to check the `realm` and most likely the `www-authenticate` header response although it depends on the OAuth2 implementation.
 
-Another important note is that this authentication system could be shared between multiple session managers. For example, you may need to use both a `default` and `ephemeral` session configuration for the same set of web services. The example above allows the same `oauthHandler` instance to be shared across multiple session managers to manage the single refresh flow.
+Another important note is that this authentication system could be shared between multiple sessions. For example, you may need to use both a `default` and `ephemeral` session configuration for the same set of web services. The example above allows the same `oauthHandler` instance to be shared across multiple sessions to manage the single refresh flow.
 
 ### Custom Response Serialization
 
@@ -831,12 +828,12 @@ let serverTrustPolicies: [String: ServerTrustPolicy] = [
     "insecure.expired-apis.com": .disableEvaluation
 ]
 
-let sessionManager = SessionManager(
+let session = Session(
     serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
 )
 ```
 
-> Make sure to keep a reference to the new `SessionManager` instance, otherwise your requests will all get cancelled when your `sessionManager` is deallocated.
+> Make sure to keep a reference to the new `Session` instance, otherwise your requests will all get cancelled when your `session` is deallocated.
 
 These server trust policies will result in the following behavior: