如何使用 Alamofire 重试请求?

Posted

技术标签:

【中文标题】如何使用 Alamofire 重试请求?【英文标题】:How to retry request with Alamofire? 【发布时间】:2019-04-22 13:52:13 【问题描述】:

如果第一个请求的响应代码是 401,在 Alamofire 中是否有办法重新发送请求,我可以在其中刷新令牌并再次重试我的请求?

问题是我已经在使用 MVVM 和完成处理程序。 在我的 ViewModel 中,请求函数如下所示:

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)
        
    

在我的 ViewController 中,我将其称为:

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

所以我不知道如何在不使用新功能的情况下重试我的请求,所以我仍然可以从我的 ViewController 中的completion 部分获得我的user 响应。 也许有人可以告诉我正确的道路。 提前致谢!

【问题讨论】:

【参考方案1】:

如果函数收到 401,你能递归调用它吗?你肯定需要创建某种类型的退出条件,这样如果它继续失败,它就会爆发,但在我看来它会起作用。

【讨论】:

【参考方案2】:

是的,你可以在 Alamofire 4.0 上使用

RequestRetrier 协议允许重试在执行时遇到错误的请求。当同时使用 RequestAdapter 和 RequestRetrier 协议时,您可以为 OAuth1、OAuth2、Basic Auth 甚至指数退避重试策略创建凭证刷新系统。可能性是无止境。下面是如何为 OAuth2 访问令牌实现刷新流程的示例。

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)
    

参考:AlamofireDocumentation

【讨论】:

【参考方案3】:

你可以添加拦截器

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

添加协议RequestInterceptor

然后实现这两个协议方法

// 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

一旦 api 得到这两个方法调用失败,做

【讨论】:

感谢您发布此答案!在这里澄清一些事情会很好:1.)什么是拦截器? (一个模块?一个概念?) 2.) 在顶部添加代码的位置,以Alamofire.request 3.) 最后,它说“做”,但没有说要做什么。跨度> 【参考方案4】:

要重试请求,请创建一个请求包装器并像这样使用 Alamofire 的 RequestInterceptor 协议

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)) 
   

用法:对于每个 API 请求,都会调用 adapt() 方法,并在 validate() 上使用 retry 方法来验证状态码。 retryLimit 可以通过在此处创建拦截器的实例来设置 如果响应为错误,则提供 retryLimit 将调用 API 两次

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)
    

【讨论】:

以上是关于如何使用 Alamofire 重试请求?的主要内容,如果未能解决你的问题,请参考以下文章

修改有效载荷的 Alamofire 重试请求

Alamofire 重试请求 - 反应方式

Alamofire 5 调整和重试请求

Alamofire 4 重试器和适配器无法看到更改的 accessToken

保存并重新发送 Alamofire 请求

如何使用操作查询发出多个 Alamofire 请求