Alamofire自动刷新令牌并重试iOS Swift 4中的先前API调用

时间:2019-07-10 05:09:58

标签: ios swift alamofire alamofire-request

现在我正在Swift 4中开发一个iOS应用程序。在这里,我正在使用Alamofire集成API调用。我需要集成正确的方法来自动刷新身份验证令牌并重试以前的API调用。成功登录后,我将存储身份验证令牌。因此,登录后,在每个API中,我都将令牌附加在标题部分中。当令牌过期时,我将得到401。那时候,我需要自动刷新身份验证令牌并再次调用相同的API。我怎样才能做到这一点?我检查了Stackoverflow,但没有任何解决方法。

这是我的API调用,

loginButton!.addTarget(self, action: #selector(onLoginButtonPress(sender:)), for: .touchUpInside)

请帮助我。如果您能用我的代码解释一下,那就太好了。

2 个答案:

答案 0 :(得分:2)

@ m1sh0的回答对我很有帮助。我只是添加了OP在注释中要求的缺少的详细信息:您如何发出Alamofire请求,以便它使用Retrier和Adapter?

我基本上使用@ m1sh0的示例,并这样称呼它:

        var request_url = Constants.API_URL + "/path/to/resource"

        let sessionManager = Alamofire.SessionManager.default
        sessionManager.adapter = MyRequestAdapter.shared
        
        sessionManager.request(request_url).validate().responseJSON { (response: DataResponse<Any>) in
            switch(response.result) {
            case .success(_):
                print(response.result.value!)
                completion(response.result.value!)
            case .failure(_):
                print(response.result.error!)
                completion(response.result.error!)
                break
            }
        }

请注意,您需要在请求中使用validate()才能重试失败。没有它,响应仅返回完成。还要注意,响应块中存在所有非401错误的故障情况,因为它们被认为是不可恢复的。

答案 1 :(得分:1)

您需要Alamofire RequestRetrier和RequestAdapter检查here

这是我的一些例子:

import UIKit
import Alamofire

class MyRequestAdapter: RequestAdapter, RequestRetrier {
    private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?) -> Void

    private let lock = NSLock()

    private var isRefreshing = false
    private var requestsToRetry: [RequestRetryCompletion] = []
    var accessToken:String? = nil
    var refreshToken:String? = nil
    static let shared = MyRequestAdapter()

    private init(){
        let sessionManager = Alamofire.SessionManager.default
        sessionManager.adapter = self
        sessionManager.retrier = self
    }

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest

        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(BASE_URL), !urlString.hasSuffix("/renew") {
            if let token = accessToken {
                urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
            }
        }
        return urlRequest
    }


    // MARK: - RequestRetrier

    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 in
                    guard let strongSelf = self else { return }

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

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

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

    // MARK: - Private - Refresh Tokens

    private func refreshTokens(completion: @escaping RefreshCompletion) {
        guard !isRefreshing else { return }

        isRefreshing = true

        let urlString = "\(BASE_URL)token/renew"

        Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: ["Authorization":"Bearer \(refreshToken!)"]).responseJSON { [weak self] response in
            guard let strongSelf = self else { return }
            if
                let json = response.result.value as? [String: Any],
                let accessToken = json["accessToken"] as? String
            {
                completion(true, accessToken)
            } else {
                completion(false, nil)
            }
            strongSelf.isRefreshing = false
        }

    }
}

我的示例有点复杂,但是是的,总的来说,我们有两个重要的方法,第一个是adapt(_ urlRequest: URLRequest) throws -> URLRequest,我们在其中附加了令牌,这里有自定义逻辑,其中服务之一不应附加此令牌作为标题。第二种方法是func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion),其中我检查错误代码是什么(在我的示例401中)。然后我用

刷新令牌
 private func refreshTokens(completion: @escaping RefreshCompletion)

对于我来说,我具有刷新令牌和访问令牌,并且当我使用刷新令牌调用服务时,我不应该在标头中附加旧的访问令牌。我认为这不是最佳做法,但它是从我不知道的peopele实现的。