防抖搜索场景下加载指示器的隐藏问题
解决防抖场景下加载指示器的竞态问题
你的核心问题是防抖逻辑中未跟踪活跃请求的状态:每次修改筛选条件就立刻显示加载,但旧请求完成时会直接隐藏加载,完全忽略是否有新的请求在执行,导致加载状态提前消失。
下面提供两种可行的解决方案:
方案一:请求计数法(线程安全)
通过维护一个活跃请求计数器,只有当所有请求都完成(计数器归0)时才隐藏加载指示器。
代码修改:
- 添加线程安全的计数器和同步队列:
private let searchQueue = DispatchQueue(label: "com.blablah.searchQueue") private var searchWorkItem: DispatchWorkItem? // 新增:用于同步请求计数的串行队列 private let requestCountSyncQueue = DispatchQueue(label: "com.blablah.requestCountSync") private var activeRequestCount = 0
- 修改
search()方法,在发起请求前增加计数:
private func search() { // 只要触发筛选,立刻显示加载状态 bottomSheetView.showGameBtn.isLoading = true self.searchWorkItem?.cancel() let workItem = DispatchWorkItem { [weak self] in guard let self = self else { return } // 线程安全地增加活跃请求数 self.requestCountSyncQueue.sync { self.activeRequestCount += 1 } self.presenter?.getSearchFilterResults() } self.searchQueue.asyncAfter(deadline: .now() + 0.5, execute: workItem) self.searchWorkItem = workItem }
- 修改回调方法,计数归0时才隐藏加载:
// Callback func showSearchFilterResults() { requestCountSyncQueue.sync { [weak self] in guard let self = self else { return } self.activeRequestCount -= 1 let shouldHideLoading = self.activeRequestCount == 0 // 回到主线程更新UI DispatchQueue.main.async { if shouldHideLoading { self.bottomSheetView.showGameBtn.isLoading = false } } } }
方案二:请求标识法(精准跟踪最后一次请求)
给每个请求分配唯一ID,仅当最后一次发起的请求完成时,才隐藏加载指示器。
代码修改:
- 添加存储最后一次请求ID的变量:
private let searchQueue = DispatchQueue(label: "com.blablah.searchQueue") private var searchWorkItem: DispatchWorkItem? // 新增:存储最后一次请求的唯一标识 private var lastRequestID: UUID = UUID()
- 修改
search()方法,生成新ID并传递给Presenter:
private func search() { bottomSheetView.showGameBtn.isLoading = true self.searchWorkItem?.cancel() let currentRequestID = UUID() let workItem = DispatchWorkItem { [weak self] in guard let self = self else { return } // 更新最后一次请求的ID self.lastRequestID = currentRequestID // 将ID传递给Presenter,让回调时带回 self.presenter?.getSearchFilterResults(with: currentRequestID) } self.searchQueue.asyncAfter(deadline: .now() + 0.5, execute: workItem) self.searchWorkItem = workItem }
- 调整Presenter的API方法,回调时带回请求ID:
// 假设Presenter的方法修改为: func getSearchFilterResults(with requestID: UUID) { // 执行API请求,完成后调用回调并传回requestID someAPICall { [weak self] result in self?.view?.showSearchFilterResults(for: requestID) } } // 对应VC的回调方法修改: func showSearchFilterResults(for requestID: UUID) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } // 仅当当前完成的是最后一次请求时,才隐藏加载 if self.lastRequestID == requestID { self.bottomSheetView.showGameBtn.isLoading = false } } }
方案对比
- 请求计数法:无需修改Presenter逻辑,适合需要保留所有请求结果的场景,但需注意线程安全。
- 请求标识法:逻辑更精准,仅关注最后一次请求的结果,适合防抖场景中只需要最新数据的需求,但需要Presenter配合传递请求ID。
内容的提问来源于stack exchange,提问作者Bobby Redjeans
相关产品推荐
相关产品推荐

