使用Combine时TableView不刷新,添加延迟后恢复正常的问题求助
TableView无法通过@Published订阅直接刷新的原因与解决方法
问题场景
定义了@Published变量存储接口返回数据:
@Published var allJobs:[JobsModel] = []
接口响应后会填充该变量,对应的订阅代码如下:
viewModel.$allJobs.subscribe(on: DispatchQueue.main ).sink(receiveCompletion: { completion in switch completion { case .finished: print("finished") break case .failure(let anError): print(anError.localizedDescription) break } }, receiveValue: { someValue in self.jobsTbl.reloadData() print(".sink() received \(someValue)") }).store(in: &cancellable)
直接调用reloadData()无法触发TableView刷新,但给刷新操作添加0.01秒延迟后,TableView就能正常更新:
viewModel.$allJobs.subscribe(on: DispatchQueue.main ).sink(receiveCompletion: { completion in switch completion { case .finished: print("finished") break case .failure(let anError): print(anError.localizedDescription) break } }, receiveValue: { someValue in DispatchQueue.main.asyncAfter(deadline: .now() + 0.01){ self.jobsTbl.reloadData() } print(".sink() received \(someValue)") }).store(in: &cancellable)
原因分析
- UI线程调度时机冲突:
@Published触发sink回调时,主线程可能还在处理数据赋值的后续操作,或者正处于UIKit布局周期的中间阶段。此时调用reloadData(),TableView的数据源还未完全稳定,UI系统暂时无法响应刷新请求,导致刷新无效。 - RunLoop模式差异:虽然指定了
subscribe(on: DispatchQueue.main),但@Published发送值的时机可能处于RunLoop的common模式,而TableView的刷新需要在default模式下执行。直接调用reloadData()会被当前RunLoop的任务阻塞,延迟0.01秒相当于把刷新任务放到下一个RunLoop周期,避开了模式冲突。
解决方法
方法1:用DispatchQueue.main.async替代固定延迟
不需要硬编码延迟时间,把刷新任务放到当前RunLoop的末尾执行,确保数据源已稳定且UI处于可刷新状态:
viewModel.$allJobs .subscribe(on: DispatchQueue.main) .sink(receiveCompletion: { completion in switch completion { case .finished: print("finished") case .failure(let anError): print(anError.localizedDescription) } }, receiveValue: { [weak self] someValue in DispatchQueue.main.async { self?.jobsTbl.reloadData() } print(".sink() received \(someValue)") }) .store(in: &cancellable)
方法2:规范使用receive(on:)指定接收线程
subscribe(on:)控制的是订阅操作的线程,而receive(on:)才是指定接收值并执行回调的线程。即使都是主线程,用receive(on:)能确保回调在正确的UI线程上下文执行,避免潜在的调度问题:
viewModel.$allJobs .receive(on: DispatchQueue.main) .sink(receiveCompletion: { completion in switch completion { case .finished: print("finished") case .failure(let anError): print(anError.localizedDescription) } }, receiveValue: { [weak self] someValue in self?.jobsTbl.reloadData() print(".sink() received \(someValue)") }) .store(in: &cancellable)
方法3:改用UITableViewDiffableDataSource(推荐)
如果项目支持iOS 13及以上,使用Diffable Data Source是更现代的方案,它能自动对比数据变化并刷新TableView,不需要手动调用reloadData(),从根源上避免这类时机问题:
// 先定义分区类型(示例) enum Section { case main } // 初始化Diffable Data Source var dataSource: UITableViewDiffableDataSource<Section, JobsModel>! // 在视图初始化时配置 dataSource = UITableViewDiffableDataSource(tableView: jobsTbl) { tableView, indexPath, job in let cell = tableView.dequeueReusableCell(withIdentifier: "JobCell", for: indexPath) // 配置cell内容 cell.textLabel?.text = job.title return cell } // 订阅数据并更新快照 viewModel.$allJobs .receive(on: DispatchQueue.main) .sink { [weak self] jobs in var snapshot = NSDiffableDataSourceSnapshot<Section, JobsModel>() snapshot.appendSections([.main]) snapshot.appendItems(jobs) self?.dataSource.apply(snapshot, animatingDifferences: true) } .store(in: &cancellable)
内容的提问来源于stack exchange,提问作者Suleman Ali
相关产品推荐
相关产品推荐

