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

Swift Combine实现Token刷新时Sink返回错误问题排查

问题分析与修复方案

1. 401错误判断逻辑失效

你当前用error as? NetworkError == .unauthorised(code: 401, error: "Unauthorized")判断401错误,但带关联值的枚举默认未实现Equatable,且服务器返回的错误消息可能和硬编码的"Unauthorized"不一致,导致判断不成立,直接抛出错误到下游。

修复:
改用模式匹配直接检查错误类型和状态码:

.tryCatch { [weak self] error -> AnyPublisher<Data, NetworkError> in
    guard let self = self else {
        throw error as? NetworkError ?? .unknown(code: -1, error: "实例已释放")
    }
    
    // 正确识别401未授权错误
    guard let networkError = error as? NetworkError,
          case .unauthorised(let code, _) = networkError,
          code == 401 else {
        throw error as? NetworkError ?? .unknown(code: -1, error: error.localizedDescription)
    }
    
    // 后续刷新逻辑
}

2. 刷新后请求未携带新Token

如果updateRequest(req)没有正确将新Token注入请求头,重新发起的请求依然会返回401,导致流程失败。

确认updateRequest逻辑:
确保方法正确读取新Token并更新请求头:

private func updateRequest(_ req: NetworkRequest) -> NetworkRequest {
    var updatedReq = req
    guard let token = UserDefaults.token else { return updatedReq }
    updatedReq.headers["Authorization"] = "Bearer \(token)"
    return updatedReq
}

3. 修正后的完整tryCatch代码块

整合上述修复,确保刷新后重新请求的逻辑正确:

.tryCatch { [weak self] error -> AnyPublisher<Data, NetworkError> in
    guard let self = self else {
        throw error as? NetworkError ?? .unknown(code: -1, error: "实例已释放")
    }
    
    guard let networkError = error as? NetworkError,
          case .unauthorised(let code, _) = networkError,
          code == 401 else {
        throw error as? NetworkError ?? .unknown(code: -1, error: error.localizedDescription)
    }
    
    return self.refreshToken
        .flatMap { _ -> AnyPublisher<Data, NetworkError> in
            let updatedReq = self.updateRequest(req)
            return self.request(updatedReq)
        }
        .eraseToAnyPublisher()
}

4. 额外优化:避免重复刷新Token

如果多个请求同时触发401,当前代码会重复调用刷新接口。可以用锁和共享Publisher避免重复请求:

private var refreshTokenPublisher: AnyPublisher<Void, NetworkError>?
private let refreshTokenLock = DispatchQueue(label: "com.yourapp.refreshTokenLock")

private func refreshTokenIfNeeded() -> AnyPublisher<Void, NetworkError> {
    return refreshTokenLock.sync {
        if let existing = refreshTokenPublisher {
            return existing
        }
        
        let publisher = refreshToken
            .handleEvents(receiveCompletion: { [weak self] _ in
                self?.refreshTokenLock.sync {
                    self?.refreshTokenPublisher = nil
                }
            })
            .share()
            .eraseToAnyPublisher()
        
        refreshTokenPublisher = publisher
        return publisher
    }
}

之后在tryCatch中用refreshTokenIfNeeded()替代refreshToken即可。

验证步骤

  1. 调试时打印updateRequest返回的请求头,确认新Token已正确注入
  2. 检查重新发起的请求状态码,确认不再返回401
  3. 若使用了重复刷新优化,可同时触发多个请求验证是否只发起一次刷新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 07:37:04