Fetch与WebSocket更新同一数据源,是否需用串行队列保障数据有效性?
多途径更新ViewModel数据源的最优方案
你提出的统一分发到同一个更新函数+异步串行队列的方案,就是确保数据源线程安全、准确实时更新的最优解之一,完全可行。
核心原因
- 串行队列能强制所有对
datasource的修改操作按顺序执行,彻底避免多线程同时读写导致的竞态条件(比如一半数据来自网络请求、一半来自WebSocket推送,造成数据错乱)。 - 把两种更新途径的结果都路由到同一个更新函数,能让数据源的修改逻辑集中管理,后续维护规则(比如全量替换还是增量更新)会更便捷。
关键补充细节
- 数据源读取也要串行化:如果其他地方需要读取
datasource(比如给UI提供数据),必须通过这个串行队列同步获取,避免读到半更新的脏数据。 - UI更新切回主队列:因为串行队列是后台队列,更新完数据源后,若要通知UI刷新(比如列表重载),一定要切换回主队列执行,否则会触发UI线程错误。
- 明确全量/增量更新逻辑:在统一的更新函数里要区分场景,比如网络请求返回全量数据时直接替换,WebSocket推送增量数据时做diff后更新对应元素,这个逻辑必须在串行队列里完成,保证操作的原子性。
修正优化后的示例代码
// 串行队列:保证所有数据源操作的线程安全 private let datasourceSerialQueue = DispatchQueue(label: "com.blah.className.datasourceSerialQueue") // 私有数据源:外部只能通过指定方法访问 private var _datasource: [MyData] = [] // 对外只读访问器:通过串行队列同步获取最新数据 var datasource: [MyData] { return datasourceSerialQueue.sync { _datasource } } func fetchData() { // 模拟网络请求异步回调 someAPIClient.fetch { [weak self] fetchResult in self?.updateDataSource(with: fetchResult, isFullUpdate: true) } } func websocketDataDidUpdate(_ socketResult: [MyData]) { // WebSocket推送的增量数据处理 self.updateDataSource(with: socketResult, isFullUpdate: false) } private func updateDataSource(with data: [MyData], isFullUpdate: Bool) { datasourceSerialQueue.async { [weak self] in guard let self = self else { return } if isFullUpdate { // 全量替换:直接覆盖数据源 self._datasource = data } else { // 增量更新:按业务规则做diff(比如按ID匹配替换/新增) for newItem in data { if let index = self._datasource.firstIndex(where: { $0.id == newItem.id }) { self._datasource[index] = newItem } else { self._datasource.append(newItem) } } } // 更新完成后切回主队列通知UI刷新 DispatchQueue.main.async { self.notifyUIDidUpdate() } } } private func notifyUIDidUpdate() { // 实现UI刷新逻辑,比如通知ViewController重载列表 }
进阶方案(Swift 5.5+)
如果使用Swift 5.5及以上版本,可以用Actor替代串行队列,语法更简洁,线程安全保障更原生:
actor DataSourceManager { private var datasource: [MyData] = [] func update(with data: [MyData], isFullUpdate: Bool) { if isFullUpdate { datasource = data } else { for newItem in data { if let index = datasource.firstIndex(where: { $0.id == newItem.id }) { datasource[index] = newItem } else { datasource.append(newItem) } } } } func getCurrentData() async -> [MyData] { return datasource } } class MyViewModel { private let dataManager = DataSourceManager() func fetchData() { someAPIClient.fetch { [weak self] fetchResult in Task { await self?.dataManager.update(with: fetchResult, isFullUpdate: true) self?.notifyUIDidUpdate() } } } func websocketDataDidUpdate(_ socketResult: [MyData]) { Task { await dataManager.update(with: socketResult, isFullUpdate: false) notifyUIDidUpdate() } } private func notifyUIDidUpdate() { // UI刷新逻辑 } }
内容的提问来源于stack exchange,提问作者chris P
相关产品推荐
相关产品推荐

