Alamofire auto refresh token and retry previous API call in iOS Swift 4

送分小仙女□ 提交于 2021-01-20 16:58:28

问题


now I'm working on an iOS application in Swift 4. Here I'm using Alamofire to integrate the API calls. I need to integrate the right way to auto-refresh the authentication token and retry the previous API calls. I'm storing the authentication token once I logged in successfully. So after login, in each API, I'm appending the token in the header part. And when if the token is expired I will get 401. That time I need to auto-refresh the authentication token and recall the same API again. How can I do that? I checked in the Stackoverflow, but I didn't get any solution.

Here's my API Call,

import Foundation
import Alamofire
import SwiftyJSON

class LoveltyAPI {

    let loveltyURL = Bundle.main.object(forInfoDictionaryKey: "APIUrlString") as! String  // Main URL
    let buildVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String  //infoDictionary?["CFBundleShortVersionString"] as AnyObject
    weak var delegate:LoveltyProtocol?  

    func get_profile(app_user_id:String, token:String) {
        let urlString = "\(loveltyURL)\(get_profile_string)?app_user_id=\(app_user_id)"
        let headers = ["Content-Type":"application/json","X-Requested-With":"XMLHttpRequest", "Authentication":"Token \(token)"]
        Alamofire.request(urlString, method: .get, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
            switch response.result {
            case .success:
                let swiftyJsonVar = JSON(response.result.value!)
                switch response.response?.statusCode {
                case 200, 201:
                    self.delegate?.getUserProfile!(response: swiftyJsonVar["data"].dictionaryObject as AnyObject)
                case 401:
                    self.delegate?.tokenExpired(response: tokenExpired as AnyObject)
                case 404:
                    self.delegate?.serviceError!(response: swiftyJsonVar["message"] as AnyObject)
                case 422:
                    self.delegate?.serviceError!(response: swiftyJsonVar["error"] as AnyObject)
                case 503:
                    self.delegate?.appDisabled(response: swiftyJsonVar.dictionaryObject as AnyObject)
                default:
                    self.delegate?.serviceError!(response: self.serverError as AnyObject)
                }
            case .failure(let error):
                self.delegate?.serviceError!(response: self.serverError as AnyObject)
            }
        }
    }
}

Please help me. If you can explain with my code, it would be very nice.


回答1:


You need Alamofire RequestRetrier and RequestAdapter check here

This is some example that I have:

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
        }

    }
}

My example is a little bit complex, but yes in general we have two important methods first one is adapt(_ urlRequest: URLRequest) throws -> URLRequest where we attaching the token, here I have custom logic where one of the services have should not attach this token as a header. The second method is func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) where I check what is the error code(in my example 401). And then I refresh my tokens with

 private func refreshTokens(completion: @escaping RefreshCompletion)

In my case, I have refresh token and access token and when I call the service with refresh token I should not append my old access token in the header. I think this is not best practice but it was implemented from peopele that I don't know.




回答2:


@m1sh0's answer was extremely helpful to me. I'm just adding the missing detail the OP asked for in the comments: How do you make the Alamofire request so that it uses the Retrier and Adapter?

I basically used @m1sh0's example and called it like this:

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

Note that you need validate() in the request in order to get retried on failure. Without it, the response is just returned for completion. Also note there's a failure case in the response block for all non-401 errors, as they are presumed unrecoverable.



来源:https://stackoverflow.com/questions/56963748/alamofire-auto-refresh-token-and-retry-previous-api-call-in-ios-swift-4

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!