TableView reloadData正确时机:基于DispatchGroup优化数据加载
使用DispatchGroup实现批量数据拉取后仅刷新一次TableView
嘿,这个需求用DispatchGroup绝对是最优解!它能帮你精准追踪所有异步数据请求的完成状态,等所有请求都结束后再统一刷新TableView,彻底解决之前频繁刷新带来的性能损耗和视觉卡顿问题。下面是具体的实现步骤和代码示例:
1. 初始化核心变量
首先在你的ViewController里定义好存储数据的数组,以及一个DispatchGroup实例:
var events: [Event] = [] // 这里假设Event是你的数据模型类/结构体 let dataFetchGroup = DispatchGroup() // 给group起个有意义的名字,可读性更好
2. 改造数据拉取逻辑
不管你是单次拉取多组数据,还是分页拉取多次,都要把每个异步请求纳入DispatchGroup的管理:
// 示例:如果是分页拉取,循环发起请求 let totalPages = 3 // 假设你要拉取3页数据 for pageIndex in 1...totalPages { dataFetchGroup.enter() // 标记一个新的异步任务开始 self.fetchEventsFromServer(page: pageIndex) { [weak self] result in defer { self?.dataFetchGroup.leave() // 无论请求成功/失败,都标记任务结束 } guard let self = self else { return } switch result { case .success(let newEvents): self.events.append(contentsOf: newEvents) // 批量追加数据,暂不刷新 case .failure(let error): print("拉取第\(pageIndex)页失败:\(error.localizedDescription)") // 这里可以根据需求处理错误,比如记录日志或提示用户 } } }
如果你的fetchEventsFromServer内部包含多个并行的子请求,同样的逻辑:每个子请求前调用enter(),完成后调用leave()即可。
3. 监听所有任务完成,统一刷新UI
通过DispatchGroup的notify方法,我们可以在所有任务都完成后触发统一的刷新操作,注意一定要回到主队列执行UI操作:
dataFetchGroup.notify(queue: DispatchQueue.main) { [weak self] in guard let self = self else { return } // 所有数据拉取&追加完成,仅刷新一次TableView self.tableView.reloadData() // 还可以在这里做其他收尾工作,比如隐藏加载指示器、更新空状态等 }
必看的关键细节
enter()和leave()必须严格配对:每调用一次enter(),就必须对应一次leave(),哪怕请求失败、抛出异常也要执行,否则DispatchGroup会一直处于等待状态,notify永远不会触发。用defer块是最稳妥的方式,确保无论代码走哪个分支都会执行leave()。- UI操作务必在主队列:TableView的刷新属于UI操作,必须放在主队列执行,否则会导致UI错乱甚至崩溃。
- 避免循环引用:闭包里使用
[weak self]捕获,防止ViewController因为被闭包强引用而无法释放。
这样改造后,你就再也不会出现每次追加数据就刷新TableView的问题了,所有数据准备好后才会触发一次刷新,既提升了性能,也优化了用户体验!
内容的提问来源于stack exchange,提问作者Ron Baker
相关产品推荐
相关产品推荐

