DebounceThrottle 工具类,防抖/节流控制
仅供参考
-
在 iOS 13 及以上系统使用现代的
Combine
实现防抖与节流; -
在 iOS 12 及以下系统回退使用
GCD
(DispatchWorkItem
)实现同样的功能;
通用 DebounceThrottle
类(Combine + GCD 混合实现)
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?
/// 防抖:延迟执行,期间再次触发将重置
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)
}
}
/// 节流:一段时间内只执行一次
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()
}
}
用法(无需关心系统版本)
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. 页面跳转节流
func goToNextPage(from vc: UIViewController) {
debouncer.throttle(for: 1.0) {
let next = UIViewController()
vc.present(next, animated: true)
}
}
下面是加强版的 DebounceThrottle
工具类,支持多任务并行防抖/节流控制。
功能增强版说明
- 支持 iOS 9+,iOS 13+ 使用 Combine
-
支持多任务 ID:你可以给不同的逻辑分配不同的
id
,互不干扰 - 支持 debounce 与 throttle
- 接口统一,使用简单
DebounceThrottle
多任务支持完整实现
import Foundation
import Combine
/// 多任务防抖 + 节流控制器
class DebounceThrottle {
/// 存储每个任务 ID 对应的 debounce 控制器(iOS13+: Combine, iOS12-: DispatchWorkItem)
private var debounceCancellables: [String: Any] = [:]
/// 存储每个任务 ID 上次 throttle 执行时间
private var throttleTimestamps: [String: Date] = [:]
/// 默认间隔时间
private let normalInterval = 0.5
// MARK: - 防抖(Debounce)
/// 延迟执行,若在 delay 时间内重复调用,则重置倒计时,仅执行最后一次调用
/// - Parameters:
/// - id: 用于标识任务的唯一字符串(如:"search"、"clickButton")
/// - delay: 延迟执行的时间(秒)
/// - action: 要执行的闭包
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: - 节流(Throttle)
/// 限制一定时间内只允许执行一次。多次触发仅第一次有效。
/// - Parameters:
/// - id: 用于标识任务的唯一字符串
/// - interval: 节流间隔(秒)
/// - action: 要执行的闭包
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 {
// 距离上次执行还没到间隔时间,不执行
return
}
throttleTimestamps[id] = now
action()
}
// MARK: - 取消某个防抖任务(可选)
/// 取消指定 ID 的防抖任务(如果还没触发就被取消)
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: - 清理所有任务(可选)
/// 清除所有任务(可选使用)
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("执行搜索请求")
}
// 节流示例(用于防止重复跳转、重复上传等)
controller.throttle(id: "navigation", interval: 1.0) {
print("页面跳转")
}
功能总览
方法名 | 功能 | 场景 | 说明 |
---|---|---|---|
debounce(id:delay:action:) |
防抖 | 输入搜索、按钮点击 | 重复触发仅执行最后一次 |
throttle(id:interval:action:) |
节流 | 页面跳转、点赞等 | 间隔内只执行一次 |
cancelDebounce(id:) |
取消防抖任务 | 用于手动中止等待操作 | |
clearAll() |
清理全部任务 | 页面销毁时使用 | 防止泄露 |