如何让Combine Publisher超时后不终止,仍可接收后续数据?
解决方案:用Combine实现超时占位且保留后续真实值的流
因为timeout操作符会终止流,无法接收超时后的返回值,我们可以通过合并原请求流与延迟占位流,配合过滤逻辑实现需求:超时后先发占位值更新UI,原请求返回后再发送真实值二次更新;若原请求未超时,则直接发送真实值,不触发占位逻辑。
完整代码实现
// 替换为你的API实际返回类型 typealias DataModel = YourActualModelType // 1. 共享原请求流,避免重复发起API调用 let originalApiCall4 = repository4.fetchData() .share() .eraseToAnyPublisher() // 2. 跟踪原请求是否已有输出:初始为false,有结果后切换为true let hasOriginalOutput = originalApiCall4 .map { _ in true } .prepend(false) .eraseToAnyPublisher() // 3. 延迟5秒发送占位值的流,仅在原请求未返回时生效 let timeoutPlaceholder = Just([] as [DataModel]) .delay(for: .seconds(5), scheduler: DispatchQueue.main) .combineLatest(hasOriginalOutput) .filter { _, hasOutput in // 仅当原请求还未输出结果时,才发送占位值 !hasOutput } .map { $0.0 } .eraseToAnyPublisher() // 4. 合并原请求流与占位流,得到最终的apiCall4流 let apiCall4 = Publishers.Merge(originalApiCall4, timeoutPlaceholder) .eraseToAnyPublisher() // 5. 原有CombineLatest4逻辑保持不变 let apiCall1 = repository1.fetchData().eraseToAnyPublisher() let apiCall2 = repository2.fetchData().eraseToAnyPublisher() let apiCall3 = repository3.fetchData().eraseToAnyPublisher() Publishers.CombineLatest4(apiCall1, apiCall2, apiCall3, apiCall4) .sink(receiveCompletion: { [weak self] result in switch result { case .finished: return case .failure(let error): self?.handleError(error) } }, receiveValue: { [weak self] result1, result2, result3, result4 in self?.saveResults(result1, result2, result3, result4) self?.updateUI() }) .store(in: &cancellables)
关键逻辑说明
share():让originalApiCall4的所有订阅者共享同一次API请求,避免重复调用。hasOriginalOutput:通过prepend(false)初始化状态,原请求返回结果时切换为true,用于判断是否需要发送占位值。timeoutPlaceholder:延迟5秒生成占位值,通过combineLatest和filter确保仅在原请求超时未返回时发送占位。Merge:合并两个流后,会出现两种情况:- 原请求5秒内返回:占位流被过滤,仅发送真实值,触发一次UI更新。
- 原请求超时:先发送占位值更新UI,后续原请求返回时发送真实值,再次触发UI更新。
内容的提问来源于stack exchange,提问作者swiftyboi
相关产品推荐
相关产品推荐

