简体   繁体   中英

How to retry request with Alamofire?

Is there a way, in Alamofire, to re-send the request if the response code from the first request is 401, where I can refresh the token and retry my request again?

The problem is that I'm using MVVM and also completion handler already. In my ViewModel the request function looks like:

public func getProfile(completion: @escaping (User?) -> Void) {
    guard let token = UserDefaults.standard.value(forKey: Constants.shared.tokenKey) else { return }

    let headers = ["Authorization": "Bearer \(token)"]

    URLCache.shared.removeAllCachedResponses()
    Alamofire.request(Constants.shared.getProfile, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in
        switch response.result {
        case .success:
            guard let data = response.data else { return }

            if JSON(data)["code"].intValue == 401 {
                // here I need to refresh my token and re-send the request
            } else {
                let user = User(json: JSON(data)["data"])
                completion(user)
            }

            completion(nil)
        case .failure(let error):
            print("Failure, ", error.localizedDescription)
            completion(nil)
        }
    }
}

and from my ViewController I call it like:

viewModel.getProfile { (user) in
    if let user = user {
        ...
    }
}

So I do not know how can retry my request without using a new function, so I can still get my user response from completion part in my ViewController. Maybe someone can show me the right path. Thanks in advance!

Could you just recursively call the function if it receives a 401? You would definitely need to create some type of exit condition so that if it continues to fail that it will break out, but it seems to me that it would work.

Yes you can on Alamofire 4.0

The RequestRetrier protocol allows a Request that encountered an Error while being executed to be retried. When using both the RequestAdapter and RequestRetrier protocols together, you can create credential refresh systems for OAuth1, OAuth2, Basic Auth and even exponential backoff retry policies. The possibilities are endless. Here's an example of how you could implement a refresh flow for OAuth2 access tokens.

func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
    lock.lock() ; defer { lock.unlock() }

    if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
        requestsToRetry.append(completion)

        if !isRefreshing {
            refreshTokens { [weak self] succeeded, accessToken, refreshToken in
                guard let strongSelf = self else { return }

                strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }

                if let accessToken = accessToken, let refreshToken = refreshToken {
                    strongSelf.accessToken = accessToken
                    strongSelf.refreshToken = refreshToken
                }

                strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                strongSelf.requestsToRetry.removeAll()
            }
        }
    } else {
        completion(false, 0.0)
    }
}

Reference: AlamofireDocumentation

you can add interceptor

Alamofire.request(Constants.shared.getProfile, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)

add the protocol RequestInterceptor

then implement this two protocol method

// retryCount number of time api need to retry

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
   
    completion(.success(urlRequest))
}

func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
    guard request.retryCount < retryCount else {
        completion(.doNotRetry)
        return
    }

/// Call UR API here }

once api get fail this two method call, do

To retry a request create a Request wrapper and use the RequestInterceptor protocol of Alamofire like this

final class RequestInterceptorWrapper: RequestInterceptor {

// Retry your request by providing the retryLimit. Used to break the flow if we get repeated 401 error
var retryLimit = 0

func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {

    guard let statusCode = request.response?.statusCode else { return }
    switch statusCode {
    case 200...299:
        completion(.doNotRetry)
    default:
        if request.retryCount < retryLimit {
            completion(.retry)
            return
        }
        completion(.doNotRetry)
    }
}

//This method is called on every API call, check if a request has to be modified optionally

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
    //Add any extra headers here 
    //urlRequest.addValue(value: "", forHTTPHeaderField: "")
    completion(.success(urlRequest)) 
   }
}

Usage: For every API request, the adapt () method is called, and on validate () the retry method is used to validate the status code. retryLimit can be set by creating an instance of the interceptor here Providing the retryLimit would call the API twice if the response was an error

let interceptor = RequestInterceptorWrapper() 
func getDataFromAnyApi(completion: @escaping (User?) -> Void)) {
    interceptor.retryLimit = 2
    AF.request(router).validate().responseJSON { (response) in
          
         guard let data = response.data else { 
            completion(nil)
            return 
         }
         // convert to User and return
         completion(User)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM