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

Swift Combine带返回值完成处理器:403自动重登并重发API请求方案

解决方案

核心思路是把回调式的登录方法转换为Combine生态兼容的Publisher,再通过链式调用实现登录完成后重发原请求的逻辑,同时处理并发请求下的重复登录问题。

1. 先对回调式login做Publisher包装

首先给SigninViewModel扩展一个返回Combine Publisher的方法,用Future类型适配原有回调逻辑:

extension SigninViewModel {
    func loginPublisher() -> AnyPublisher<Bool, Error> {
        return Future { [weak self] promise in
            self?.login { success in
                if success {
                    promise(.success(true))
                } else {
                    promise(.failure(Error.errorCode(statusCode: 403, errorMessage: "登录刷新失败")))
                }
            }
        }
        .eraseToAnyPublisher()
    }
}

2. 共享SigninViewModel实例避免重复登录

不要在每次403时都创建新的SigninViewModel实例,建议在APIService层持有一个共享的实例,同时添加登录状态控制,避免多个403请求同时触发登录:

// 在你的APIService类中添加属性
private let signinModel = SigninViewModel()
private let isLoggingIn = CurrentValueSubject<Bool, Never>(false)

3. 改造request方法的403处理分支

把原来403分支的回调逻辑替换为Publisher链式调用,登录成功后重发当前请求,失败则返回错误:

else if response.statusCode == 403 {
    // 先判断是否正在登录,避免重复触发
    guard !isLoggingIn.value else {
        // 等待登录完成后重试
        return isLoggingIn
            .filter { !$0 }
            .first()
            .flatMap { _ in 
                // 重发当前请求
                return self.request(ofType: type, from: endpoint, body: body)
            }
            .eraseToAnyPublisher()
    }
    
    isLoggingIn.send(true)
    // 读取存储的凭证
    guard let credentials = KeychainStorage.getCredentials() else {
        isLoggingIn.send(false)
        return Fail(error: Error.errorCode(statusCode: 401, errorMessage: "未找到登录凭证,请重新登录")).eraseToAnyPublisher()
    }
    signinModel.credentials = credentials
    
    return signinModel.loginPublisher()
        .receive(on: DispatchQueue.main)
        .handleEvents(receiveCompletion: { [weak self] _ in
            self?.isLoggingIn.send(false)
        })
        .flatMap { success -> AnyPublisher<T, Error> in
            guard success else {
                return Fail(error: Error.errorCode(statusCode: 403, errorMessage: "登录凭证已过期,请重新登录")).eraseToAnyPublisher()
            }
            // 登录成功,重发原始请求
            return self.request(ofType: type, from: endpoint, body: body)
        }
        .eraseToAnyPublisher()
}

注意事项

  • 递归调用request方法不会产生死循环:如果登录后依然返回403,会触发登录失败逻辑,最终返回错误终止流程
  • 可以根据业务需求添加重试次数限制,避免无限重试
  • 建议把全局的token存储、登录状态都放到统一的服务层管理,不要分散在ViewModel中,避免状态不同步的问题

内容的提问来源于stack exchange,提问作者Björn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 12:06:00