You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

防抖搜索场景下加载指示器的隐藏问题

解决防抖场景下加载指示器的竞态问题

你的核心问题是防抖逻辑中未跟踪活跃请求的状态:每次修改筛选条件就立刻显示加载,但旧请求完成时会直接隐藏加载,完全忽略是否有新的请求在执行,导致加载状态提前消失。

下面提供两种可行的解决方案:


方案一:请求计数法(线程安全)

通过维护一个活跃请求计数器,只有当所有请求都完成(计数器归0)时才隐藏加载指示器。

代码修改:

  1. 添加线程安全的计数器和同步队列:
private let searchQueue = DispatchQueue(label: "com.blablah.searchQueue")
private var searchWorkItem: DispatchWorkItem?
// 新增:用于同步请求计数的串行队列
private let requestCountSyncQueue = DispatchQueue(label: "com.blablah.requestCountSync")
private var activeRequestCount = 0
  1. 修改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
}
  1. 修改回调方法,计数归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,仅当最后一次发起的请求完成时,才隐藏加载指示器。

代码修改:

  1. 添加存储最后一次请求ID的变量:
private let searchQueue = DispatchQueue(label: "com.blablah.searchQueue")
private var searchWorkItem: DispatchWorkItem?
// 新增:存储最后一次请求的唯一标识
private var lastRequestID: UUID = UUID()
  1. 修改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
}
  1. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:22:53