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

如何延迟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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 14:35:27