Alamofire请求遇401自动刷新Token并重发实现方案问询
解决Alamofire请求401自动刷新Token并重发的问题
问题说明
使用Alamofire构建请求构造器时,请求偶尔返回401状态码(需刷新Token),希望修改Network类实现Token自动刷新并重发请求,但存在两个问题:
- 无法注入loginService
- 不清楚如何在管理器中实现请求重发
已尝试实现RequestInterceptor扩展,但不知道如何让其生效。
修改后的完整Network类代码
import Alamofire class Network { typealias Method = Alamofire.HTTPMethod typealias Headers = HTTPHeaders typealias ResponseResult = Result<Response, Error> typealias Completion = (ResponseResult) -> Void // 重试次数限制 private let retryLimit = 1 // 持有Alamofire Session,将自身作为Interceptor传入 private lazy var session: Session = { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 30 return Session(configuration: configuration, interceptor: self) }() lazy var baseURL: URL = { #if PROD return URL(string: "https://xxxxx.xxxx.com/")! #else return URL(string: "https://xxxxx.xxxx.com/")! #endif }() func request(_ target: RequestConvertible, queue: DispatchQueue = .main, completion: @escaping Completion) { do { let request = try makeURLRequest(for: target) // 使用自定义session发起请求,而非AF.request performRequest(session.request(request), queue: queue, target: target, completion: completion) } catch { completion(.failure(error)) } } func requestWithDecode<T: Decodable>(_ target: RequestConvertible, value: T.Type, queue: DispatchQueue = .main, completion: @escaping (Result<T, Error>) -> Void) { request(target, queue: queue) { [weak self] result in switch result { case .success(let response): do { completion(try self?.handleResponse(response: response, value: value) ?? .failure(CommonError.undefined)) } catch { completion(.failure(error)) } case .failure(let error): completion(.failure(error)) } } } // MARK: - Private private func handleResponse<T: Decodable>(response: Response, value: T.Type) throws -> Result<T, Error> { let apiResponse = try response.decode(APIResponse<T>.self) if let result = apiResponse.data { return .success(result) } else if let error = apiResponse.errors { return .failure(error) } else { return .failure(CommonError.custom(apiResponse.errors?.message.joined(separator: " ") ?? "")) } } private func makeURLRequest(for target: RequestConvertible) throws -> URLRequest { let url = target.baseURL ?? baseURL let pathURL = target.path.isEmpty ? url : url.appendingPathComponent(target.path) var request = try URLRequest(url: pathURL).encoded(for: target) request.httpMethod = target.method.rawValue target.headers?.dictionary.forEach { request.setValue($1, forHTTPHeaderField: $0) } return request } private func performRequest(_ request: DataRequest, queue: DispatchQueue, target: RequestConvertible, completion: @escaping Completion) { request.responseData(queue: queue) { responseData in guard let response = responseData.response else { return completion(.failure(responseData.error ?? CommonError.undefined)) } completion(Result { Response(data: responseData.data ?? Data(), response: response) }) } // Alamofire的Session.request会自动启动请求,无需手动resume } // Token刷新锁,避免多个请求同时触发刷新 private var isRefreshingToken = false // 等待刷新完成的请求队列 private var pendingRequests: [(Bool) -> Void] = [] } // MARK: - RequestInterceptor 实现 extension Network: RequestInterceptor { func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) { var request = urlRequest guard let token = UserDefaultService.token()?.accessToken else { completion(.success(urlRequest)) return } let bearerToken = "Bearer \(token)" request.setValue(bearerToken, forHTTPHeaderField: "Authorization") completion(.success(request)) } func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { guard let statusCode = request.response?.statusCode else { completion(.doNotRetry) return } guard request.retryCount < retryLimit else { completion(.doNotRetryWithError(CommonError.tokenRefreshFailed)) return } switch statusCode { case 200...299: completion(.doNotRetry) case 401: handle401Retry(completion: completion) default: completion(.doNotRetry) } } // 处理401重试逻辑,解决并发刷新问题 private func handle401Retry(completion: @escaping (RetryResult) -> Void) { if isRefreshingToken { // 已有刷新请求在进行,加入等待队列 pendingRequests.append { success in completion(success ? .retry : .doNotRetryWithError(CommonError.tokenRefreshFailed)) } } else { isRefreshingToken = true refreshToken { [weak self] success in guard let self = self else { return } self.isRefreshingToken = false // 处理所有等待的请求 self.pendingRequests.forEach { $0(success) } self.pendingRequests.removeAll() // 处理当前请求 completion(success ? .retry : .doNotRetryWithError(CommonError.tokenRefreshFailed)) } } } func refreshToken(completion: @escaping (_ isSuccess: Bool) -> Void) { guard let refreshToken = UserDefaultService.token()?.refreshToken else { completion(false) return } let url = baseURL.appendingPathComponent("auth/refresh-token") let parameters: [String: Any] = ["refresh_token": refreshToken] // 使用独立Session发起刷新请求,避免触发自身Interceptor导致循环 let refreshSession = Session(configuration: .default) refreshSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default) .validate() .responseDecodable(of: Token.self) { response in switch response.result { case .success(let newToken): UserDefaultService.setToken(newToken) completion(true) case .failure: // 刷新失败,清除本地Token,后续可跳转登录页 UserDefaultService.clearToken() completion(false) } } } } // 辅助类型示例 enum CommonError: Error { case undefined case custom(String) case tokenRefreshFailed } struct Token: Decodable { let accessToken: String let refreshToken: String let expiresIn: Int } struct APIResponse<T: Decodable>: Decodable { let data: T? let errors: APIError? } struct APIError: Error, Decodable { let message: [String] } protocol RequestConvertible { var baseURL: URL? { get } var path: String { get } var method: Alamofire.HTTPMethod { get } var headers: HTTPHeaders? { get } // 可添加parameters等其他必要属性 } extension URLRequest { func encoded(for target: RequestConvertible) throws -> URLRequest { // 实现参数编码逻辑,比如根据target的parameters进行编码 return self } } struct Response { let data: Data let response: HTTPURLResponse func decode<T: Decodable>(_ type: T.Type) throws -> T { return try JSONDecoder().decode(type, from: data) } var statusCode: Int { return response.statusCode } } // UserDefault操作示例 enum UserDefaultService { static func token() -> Token? { // 从UserDefaults读取Token逻辑 return nil } static func setToken(_ token: Token) { // 保存Token到UserDefaults逻辑 } static func clearToken() { // 清除UserDefaults中的Token逻辑 } }
关键改动说明
启用Interceptor机制:
- 在Network类中创建自定义
Session,将自身作为RequestInterceptor传入,确保adapt和retry方法被Alamofire调用。 - 所有业务请求改用自定义
session.request发起,替代全局AF.request。
- 在Network类中创建自定义
解决并发刷新问题:
- 添加
isRefreshingToken标记和pendingRequests队列,避免多个401请求同时触发Token刷新。 - 刷新完成后统一处理所有等待的请求,保证Token一致性。
- 添加
完善重试与刷新逻辑:
- 限制重试次数,防止无限循环。
- 刷新失败时返回明确错误,可在回调中处理跳转登录等逻辑。
- 使用独立Session发起刷新请求,避免触发自身Interceptor导致循环。
优化冗余代码:
- 移除
performRequest中手动resume的代码,Alamofire会自动启动请求。 - 修正原有刷新逻辑中错误使用accessToken作为refreshToken的问题。
- 移除
内容的提问来源于stack exchange,提问作者Evg Enii
相关产品推荐
相关产品推荐

