Swift Observation Framework迁移:如何实现Debounce功能?
在Observation Framework中实现Debounce功能
Observation Framework本身没有原生支持debounce,它的核心是通过@Observable宏自动追踪属性变化并触发视图更新,并不提供流式处理的防抖/节流机制。下面是几种可靠的实现方式,以及解决Combine结合时出错的正确姿势:
方案1:手动用Task实现轻量Debounce
适合搜索框输入这类简单场景,不需要依赖Combine,代码更轻量:
@Observable class SearchViewModel { var searchText = "" private var debounceTask: Task<Void, Never>? func updateSearchText(_ input: String) { searchText = input // 取消未执行的防抖任务 debounceTask?.cancel() // 启动新的500ms延迟任务 debounceTask = Task { try? await Task.sleep(nanoseconds: 500_000_000) // 校验任务未被取消且文本未被新输入覆盖 guard !Task.isCancelled, self.searchText == input else { return } // 执行防抖后的业务逻辑 await performSearch(input) } } private func performSearch(_ keyword: String) async { // 发起网络请求或本地搜索 } }
方案2:正确结合Combine解决报错问题
如果仍想复用Combine的debounce操作符,关键是正确使用Observation属性的投影值,并做好生命周期管理:
import Combine import Observation @Observable class SearchViewModel { var searchText = "" private var cancellables = Set<AnyCancellable>() init() { // 通过$searchText获取属性的Combine Publisher投影 $searchText .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) .removeDuplicates() .sink { [weak self] text in guard let self = self else { return } self.handleSearch(text) } .store(in: &cancellables) } private func handleSearch(_ keyword: String) { // 处理搜索逻辑 } }
之前结合Combine出错大概率是这两个原因:
- 没有使用
$前缀获取Observation属性的Combine Publisher投影 - 未用
cancellables存储订阅,导致订阅被提前释放
方案3:封装通用Debounce工具类
如果多个ViewModel需要防抖逻辑,可以封装工具类复用:
class Debouncer { private let delay: TimeInterval private var pendingTask: Task<Void, Never>? init(delay: TimeInterval) { self.delay = delay } func execute(action: @escaping () async -> Void) { pendingTask?.cancel() pendingTask = Task { try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) guard !Task.isCancelled else { return } await action() } } } // ViewModel中使用 @Observable class SearchViewModel { var searchText = "" private let debouncer = Debouncer(delay: 0.5) func onSearchInputChanged(_ input: String) { searchText = input debouncer.execute { [weak self] in guard let self = self else { return } await self.performSearch(input) } } private func performSearch(_ keyword: String) async { // 搜索逻辑 } }
内容的提问来源于stack exchange,提问作者Stannis Baratheon
相关产品推荐
相关产品推荐

