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
相关产品推荐
相关产品推荐

