防抖/節(jié)流控制

DebounceThrottle 工具類,防抖/節(jié)流控制
僅供參考

  • 在 iOS 13 及以上系統(tǒng)使用現(xiàn)代的 Combine 實現(xiàn)防抖與節(jié)流;
  • 在 iOS 12 及以下系統(tǒng)回退使用 GCDDispatchWorkItem)實現(xiàn)同樣的功能;

通用 DebounceThrottle 類(Combine + GCD 混合實現(xiàn))

import Foundation
import Combine
import UIKit

class DebounceThrottle {
    
    // MARK: - Internal storage
    private var debounceCancellable: AnyCancellable?
    private var debounceWorkItem: DispatchWorkItem?
    private let normalInterval = 0.5

    private var lastThrottleDate: Date?

    /// 防抖:延遲執(zhí)行,期間再次觸發(fā)將重置
    func debounce(for delay: TimeInterval? = nil, action: @escaping () -> Void) {
        let delay = (delay == nil ? normalInterval : delay!)
        if #available(iOS 13.0, *) {
            debounceCancellable?.cancel()
            debounceCancellable = Just(())
                .delay(for: .seconds(delay), scheduler: RunLoop.main)
                .sink { _ in action() }
        } else {
            debounceWorkItem?.cancel()
            let item = DispatchWorkItem(block: action)
            debounceWorkItem = item
            DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: item)
        }
    }

    /// 節(jié)流:一段時間內(nèi)只執(zhí)行一次
    func throttle(for interval: TimeInterval? = nil, action: @escaping () -> Void) {
        let interval = (interval == nil ? normalInterval : interval!)
        let now = Date()
        if let last = lastThrottleDate, now.timeIntervalSince(last) < interval {
            return // 丟棄
        }
        lastThrottleDate = now
        action()
    }

    /// 取消防抖(可選)
    func cancelDebounce() {
        if #available(iOS 13.0, *) {
            debounceCancellable?.cancel()
        } else {
            debounceWorkItem?.cancel()
        }
    }

    deinit {
        cancelDebounce()
    }
}

用法(無需關(guān)心系統(tǒng)版本)

let debouncer = DebounceThrottle()

// 1. 按鈕點擊防抖
@IBAction func buttonTapped(_ sender: UIButton) {
    debouncer.debounce(for: 0.4) { [weak self] in
        self?.performAction()
    }
}

// 2. 搜索輸入防抖
func searchTextChanged(_ text: String) {
    debouncer.debounce(for: 0.3) {
        print("搜索:\(text)")
    }
}

// 3. 頁面跳轉(zhuǎn)節(jié)流
func goToNextPage(from vc: UIViewController) {
    debouncer.throttle(for: 1.0) {
        let next = UIViewController()
        vc.present(next, animated: true)
    }
}

下面是加強版的 DebounceThrottle 工具類,支持多任務(wù)并行防抖/節(jié)流控制。

功能增強版說明

  • 支持 iOS 9+,iOS 13+ 使用 Combine
  • 支持多任務(wù) ID:你可以給不同的邏輯分配不同的 id,互不干擾
  • 支持 debounce 與 throttle
  • 接口統(tǒng)一,使用簡單

DebounceThrottle 多任務(wù)支持完整實現(xiàn)

import Foundation
import Combine

/// 多任務(wù)防抖 + 節(jié)流控制器
class DebounceThrottle {
    
    /// 存儲每個任務(wù) ID 對應(yīng)的 debounce 控制器(iOS13+: Combine, iOS12-: DispatchWorkItem)
    private var debounceCancellables: [String: Any] = [:]
    
    /// 存儲每個任務(wù) ID 上次 throttle 執(zhí)行時間
    private var throttleTimestamps: [String: Date] = [:]
    
    /// 默認間隔時間
    private let normalInterval = 0.5

    // MARK: - 防抖(Debounce)
    
    /// 延遲執(zhí)行,若在 delay 時間內(nèi)重復(fù)調(diào)用,則重置倒計時,僅執(zhí)行最后一次調(diào)用
    /// - Parameters:
    ///   - id: 用于標(biāo)識任務(wù)的唯一字符串(如:"search"、"clickButton")
    ///   - delay: 延遲執(zhí)行的時間(秒)
    ///   - action: 要執(zhí)行的閉包
    func debounce(id: String, delay: TimeInterval? = nil, action: @escaping () -> Void) {
        let delay = (delay == nil ? normalInterval : delay!)
        if #available(iOS 13.0, *) {
            // iOS 13+ 使用 Combine
            (debounceCancellables[id] as? AnyCancellable)?.cancel()
            let cancellable = Just(())
                .delay(for: .seconds(delay), scheduler: RunLoop.main)
                .sink { _ in
                    action()
                }
            debounceCancellables[id] = cancellable
        } else {
            // iOS 12 及以下使用 GCD
            (debounceCancellables[id] as? DispatchWorkItem)?.cancel()
            let workItem = DispatchWorkItem(block: action)
            debounceCancellables[id] = workItem
            DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
        }
    }
    
    // MARK: - 節(jié)流(Throttle)

    /// 限制一定時間內(nèi)只允許執(zhí)行一次。多次觸發(fā)僅第一次有效。
    /// - Parameters:
    ///   - id: 用于標(biāo)識任務(wù)的唯一字符串
    ///   - interval: 節(jié)流間隔(秒)
    ///   - action: 要執(zhí)行的閉包
    func throttle(id: String, interval: TimeInterval? = nil, action: @escaping () -> Void) {
        let interval = (interval == nil ? normalInterval : interval!)
        let now = Date()
        if let lastTime = throttleTimestamps[id], now.timeIntervalSince(lastTime) < interval {
            // 距離上次執(zhí)行還沒到間隔時間,不執(zhí)行
            return
        }
        throttleTimestamps[id] = now
        action()
    }
    
    // MARK: - 取消某個防抖任務(wù)(可選)

    /// 取消指定 ID 的防抖任務(wù)(如果還沒觸發(fā)就被取消)
    func cancelDebounce(id: String) {
        if #available(iOS 13.0, *) {
            (debounceCancellables[id] as? AnyCancellable)?.cancel()
        } else {
            (debounceCancellables[id] as? DispatchWorkItem)?.cancel()
        }
        debounceCancellables.removeValue(forKey: id)
    }
    
    // MARK: - 清理所有任務(wù)(可選)

    /// 清除所有任務(wù)(可選使用)
    func clearAll() {
        if #available(iOS 13.0, *) {
            debounceCancellables.values.forEach { ($0 as? AnyCancellable)?.cancel() }
        } else {
            debounceCancellables.values.forEach { ($0 as? DispatchWorkItem)?.cancel() }
        }
        debounceCancellables.removeAll()
        throttleTimestamps.removeAll()
    }
    
    deinit {
        clearAll()
    }
}

示例

let controller = DebounceThrottle()

// 防抖示例(用于按鈕點擊、搜索輸入等)
controller.debounce(id: "search", delay: 0.3) {
    print("執(zhí)行搜索請求")
}

// 節(jié)流示例(用于防止重復(fù)跳轉(zhuǎn)、重復(fù)上傳等)
controller.throttle(id: "navigation", interval: 1.0) {
    print("頁面跳轉(zhuǎn)")
}

功能總覽

方法名 功能 場景 說明
debounce(id:delay:action:) 防抖 輸入搜索、按鈕點擊 重復(fù)觸發(fā)僅執(zhí)行最后一次
throttle(id:interval:action:) 節(jié)流 頁面跳轉(zhuǎn)、點贊等 間隔內(nèi)只執(zhí)行一次
cancelDebounce(id:) 取消防抖任務(wù) 用于手動中止等待操作
clearAll() 清理全部任務(wù) 頁面銷毀時使用 防止泄露
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容