You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alamofire 4重试器与适配器无法识别更新后的accessToken问题

解决Alamofire Retrier/Adapt并发场景下Token刷新后的请求失败问题

嘿,我猜你遇到的大概率是并发请求触发的Token竞态条件——虽然你改成了同步请求获取新Token,但如果没做好Token的线程安全访问,或者Retrier/Adapt的逻辑里没正确复用已刷新的Token,就会出现部分请求还是拿着旧Token发送的情况。下面给你几个针对性的排查和解决方向:

1. 先把Token的读写改成线程安全的

不管你用什么方式存Token(单例变量、UserDefaults啥的),多线程下的读写必须加锁,不然很容易出现一个线程已经更新了Token,但另一个线程读到的还是旧值的情况。

给你个简单的实现例子,用NSLock来保护Token的访问:

class TokenManager {
    static let shared = TokenManager()
    private let lock = NSLock()
    private var _accessToken: String?
    
    // 线程安全的Token读写属性
    var accessToken: String? {
        get {
            lock.lock()
            defer { lock.unlock() }
            return _accessToken
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            _accessToken = newValue
        }
    }
    
    // 同步刷新Token的方法,同样加锁确保原子性
    func refreshTokenSync() -> String? {
        lock.lock()
        defer { lock.unlock() }
        // 这里写你的同步请求逻辑,比如调用刷新接口
        guard let newToken = fetchNewTokenFromAPI() else { return nil }
        _accessToken = newToken
        return newToken
    }
    
    private func fetchNewTokenFromAPI() -> String? {
        // 你的同步请求代码,比如用URLSession同步请求
        return "new_valid_token"
    }
}

2. 避免重复触发Token刷新

就算是同步请求,如果多个请求同时进入Adapt/Retrier,发现Token失效,还是可能重复调用刷新接口(毕竟同步请求也需要时间)。所以得加个“正在刷新”的标记,确保同一时间只有一个请求去刷新Token,其他请求等刷新完成后直接复用新Token。

可以用DispatchSemaphore来实现这个“互斥刷新”的逻辑:

class TokenManager {
    static let shared = TokenManager()
    private let lock = NSLock()
    private let refreshSemaphore = DispatchSemaphore(value: 1)
    private var _accessToken: String?
    private var isRefreshing = false
    
    var accessToken: String? {
        // 线程安全读写,同上
    }
    
    func getValidToken() -> String? {
        refreshSemaphore.wait()
        defer { refreshSemaphore.signal() }
        
        // 先检查有没有已经刷新好的Token,避免重复刷新
        if let validToken = accessToken {
            return validToken
        }
        
        // 执行同步刷新
        guard let newToken = fetchNewTokenFromAPI() else { return nil }
        accessToken = newToken
        return newToken
    }
}

然后在你的Adaptor里,就用这个getValidToken()方法来获取Token:

class CustomAdapter: RequestAdapter {
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        guard var request = urlRequest as? URLRequest else {
            completion(.failure(NSError(domain: "AdaptError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid request"])))
            return
        }
        
        let tokenManager = TokenManager.shared
        guard let token = tokenManager.getValidToken() else {
            completion(.failure(NSError(domain: "AdaptError", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to get valid token"])))
            return
        }
        
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        completion(.success(request))
    }
}

3. 检查Retrier的重试逻辑是否正确

有时候请求失败触发重试,但如果重试时没有重新获取最新的Token,还是会用旧Token发送,自然还是失败。所以在Retrier的重试逻辑里,一定要重新拿最新的Token再修改请求头:

class CustomRetrier: RequestRetrier {
    func should(_ session: Session, retry request: Request, for error: Error, dueTo failureReason: RetryFailureReason, completion: @escaping (RetryResult) -> Void) {
        // 先判断是不是401过期错误
        guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
            completion(.doNotRetry)
            return
        }
        
        let tokenManager = TokenManager.shared
        guard let newToken = tokenManager.getValidToken() else {
            completion(.doNotRetryWithError(NSError(domain: "RetryError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to refresh token"])))
            return
        }
        
        // 更新请求的Authorization头
        var newRequest = request.request!
        newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization")
        request.request = newRequest
        
        // 重试请求
        completion(.retry)
    }
}

4. 从失败请求的细节里找线索

你提到会打印失败请求的响应,建议重点看这几个点:

  • 失败请求的Authorization头是不是旧的Token?如果是,那就是线程安全或者Token复用的问题
  • 失败的状态码是不是还是401?如果是,可能刷新接口返回的Token本身就无效(比如Refresh Token也过期了,或者刷新逻辑有bug)
  • 是不是多个请求同时触发刷新,导致某个请求拿到了中间状态的Token?

这些细节能帮你更快定位到底是哪里出了问题。


内容的提问来源于stack exchange,提问作者megha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:08:19