添加@MainActor仍无法保证线程安全,UICollectionView重复请求致崩溃
解决方案
核心思路
通过线程安全的加载状态标记避免并发请求,确保同一时间仅一个API请求执行,同时修正数据处理中的潜在问题,从根源上防止DiffableDataSource因重复标识符崩溃。
具体实现步骤
1. 添加原子性加载状态变量
在类中新增@MainActor修饰的布尔变量,保证状态读写均在主队列执行,消除多线程竞态条件:
@MainActor private var isFetching = false
2. 改造updateItems函数,增加请求拦截逻辑
发起API请求前先校验加载状态,若已有请求在执行则直接返回;通过defer确保请求结束后重置状态:
@MainActor internal func updateItems() async throws { guard !isFetching else { return } isFetching = true defer { isFetching = false } let newItems = try await NetworkService.shared.getItems(limit: 10, cursor: cursor) guard !newItems.isEmpty else { return } self.cursor = newItems.last // 修正原代码语法错误:newItems(\.id) 应为 map 调用 let newIDs = newItems.map(\.id) // 可选:过滤重复ID,进一步避免DiffableDataSource崩溃 let uniqueIDs = newIDs.filter { !self.itemIDs.contains($0) } let uniqueItems = newItems.filter { uniqueIDs.contains($0.id) } self.items.append(contentsOf: uniqueItems) self.itemIDs.append(contentsOf: uniqueIDs) self.buildSnapshot() }
3. 优化willDisplay方法,前置状态校验
触发加载前先检查是否正在请求,避免重复创建Task发起并行请求:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard !isFetching else { return } if (self.itemIDs.count - indexPath.row) < 5 { Task(priority: .userInitiated) { try await self.updateItems() } } }
4. 改进快照构建逻辑(可选)
基于现有快照增量更新,而非全量重建,提升性能:
@MainActor func buildSnapshot() { var snapshot = dataSource.snapshot() if snapshot.sectionIdentifiers.isEmpty { snapshot.appendSections([.allItems]) } snapshot.appendItems(self.items) dataSource.apply(snapshot, animatingDifferences: true) }
关键说明
@MainActor修饰的isFetching是解决问题的核心:之前使用状态标记无效,大概率是因为未保证状态读写的原子性,主队列下的状态操作能彻底避免并发冲突。defer语句确保无论请求成功、失败还是抛出异常,加载状态都会被重置,不会阻塞后续的加载触发。- 新增的重复ID过滤逻辑,即使出现极端情况的重复请求,也能避免DiffableDataSource因重复标识符崩溃。
内容的提问来源于stack exchange,提问作者Will
相关产品推荐
相关产品推荐

