如何延迟Swift属性的didSet触发,使其每秒仅执行一次?
实现Swift属性didSet的防抖(Debounce)效果
当然可以实现这种防抖效果,核心思路是每次属性变化时,取消之前还未执行的任务,重新创建一个延迟任务,只有当属性停止变化超过指定间隔(比如1秒)后,才会触发目标函数。
直接在didSet中实现
针对你给出的代码,修改如下:
import Combine class SomeViewModel: ObservableObject { @Published var someProperty: String = "" { didSet { // 取消之前的防抖任务 debounceTask?.cancel() // 创建新的1秒延迟任务 debounceTask = Task { try await Task.sleep(nanoseconds: 1_000_000_000) // 转换为纳秒 guard !Task.isCancelled else { return } // 任务被取消就终止 await someFunction(someParamater: someProperty) } } } // 保存防抖任务的引用,用于取消操作 private var debounceTask: Task<Void, Never>? private func someFunction(someParamater: String) async { // 这里写你的业务逻辑 print("触发函数,参数:\(someParamater)") } // 销毁对象时取消任务,避免内存泄漏 deinit { debounceTask?.cancel() } }
封装成可复用的属性包装器
如果需要在多个属性上使用防抖逻辑,可以封装成@propertyWrapper,更简洁:
import Combine @propertyWrapper struct DebouncedPublished<Value> { private let delay: TimeInterval @Published private var innerValue: Value private var debounceTask: Task<Void, Never>? init(wrappedValue: Value, delay: TimeInterval) { self.innerValue = wrappedValue self.delay = delay } var wrappedValue: Value { get { innerValue } set { debounceTask?.cancel() debounceTask = Task { try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) guard !Task.isCancelled else { return } self.innerValue = newValue } } } // 保留@Published的投影属性,方便订阅 var projectedValue: Published<Value>.Publisher { $innerValue } }
使用这个包装器的示例:
class SomeViewModel: ObservableObject { @DebouncedPublished(delay: 1.0) var someProperty: String = "" private var cancellables = Set<AnyCancellable>() init() { // 订阅防抖后的属性变化 $_someProperty.sink { [weak self] newValue in guard let self = self else { return } Task { await self.someFunction(someParamater: newValue) } }.store(in: &cancellables) } private func someFunction(someParamater: String) async { print("触发函数,参数:\(someParamater)") } }
两种方式的核心都是利用Task的取消机制,确保短时间内的多次属性变化只会触发最后一次的函数调用。
内容的提问来源于stack exchange,提问作者mystride
相关产品推荐
相关产品推荐

