Swift 高级处理交互防抖

210 阅读3分钟

处理swift 的交互事件当中经常要处理事件的防抖,在这里写一个扩展性比较强的专用类去处理交互防抖的处理,如果有不足之处理,还望指正。

为了更全面地处理点击、输入、滑动等用户交互事件,我们可以进一步扩展 Debouncer 类,并为不同的交互事件创建单独的示例。这样可以确保代码更加健壮、高级且具有更强的扩展性和通用性。

import Foundation

/// 防抖处理类
final class Debouncer<T> {
    // 防抖时间间隔
    private var delay: TimeInterval
    // 执行任务的队列
    private var queue: DispatchQueue
    // 当前待执行的任务
    private var workItem: DispatchWorkItem?
    // 用于管理状态的队列
    private var stateQueue: DispatchQueue
    // 当前任务的状态
    private var state: State
    
    // 任务状态的枚举
    private enum State {
        case idle         // 空闲状态
        case scheduled(UUID) // 已调度了一个任务,等待执行
        case executing    // 当前有任务正在执行
    }

    /// 初始化防抖处理类
    /// - Parameters:
    ///   - delay: 防抖延迟时间(秒)
    ///   - queue: 执行防抖任务的队列,默认为主队列
    init(delay: TimeInterval, queue: DispatchQueue = .main) {
        self.delay = delay
        self.queue = queue
        self.stateQueue = DispatchQueue(label: "debouncer.state", attributes: .concurrent)
        self.state = .idle
    }

    /// 执行防抖任务
    /// - Parameter action: 需要执行的任务闭包
    func debounce(action: @escaping (T) -> Void) -> (T) -> Void {
        return { [weak self] input in
            guard let self = self else { return }

            let identifier = UUID()
            
            // 使用屏障(barrier)确保对状态的安全修改
            self.stateQueue.async(flags: .barrier) {
                // 取消前一个任务
                self.workItem?.cancel()
                // 更新状态为已调度,带有唯一标识符
                self.state = .scheduled(identifier)
                
                // 创建一个新的任务项
                let workItem = DispatchWorkItem { [weak self] in
                    guard let self = self else { return }
                    
                    // 执行任务前,再次确认状态,确保是最新的任务
                    self.stateQueue.async(flags: .barrier) {
                        if case .scheduled(let id) = self.state, id == identifier {
                            self.state = .executing
                        }
                    }
                    
                    // 执行传入的任务
                    action(input)
                    
                    // 更新状态为空闲
                    self.stateQueue.async(flags: .barrier) {
                        self.state = .idle
                    }
                }
                
                // 保存当前任务项
                self.workItem = workItem
                // 在指定延迟后执行任务
                self.queue.asyncAfter(deadline: .now() + self.delay, execute: workItem)
            }
        }
    }
}

// 示例用法
class ExampleViewController: UIViewController {
    // 创建一个防抖处理实例,延迟时间为0.5秒
    private let clickDebouncer = Debouncer<Void>(delay: 0.5)
    private let inputDebouncer = Debouncer<String>(delay: 0.3)
    private let scrollDebouncer = Debouncer<Void>(delay: 0.2)

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupButton()
        setupTextField()
        setupScrollView()
    }
    
    // 设置按钮
    private func setupButton() {
        let button = UIButton(type: .system)
        button.setTitle("Click Me", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button)
    }
    
    // 设置文本输入框
    private func setupTextField() {
        let textField = UITextField()
        textField.borderStyle = .roundedRect
        textField.placeholder = "Type here..."
        textField.addTarget(self, action: #selector(textFieldChanged(_:)), for: .editingChanged)
    }

    // 设置滚动视图
    private func setupScrollView() {
        let scrollView = UIScrollView()
        scrollView.delegate = self
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
    }

    // 按钮点击事件处理
    @objc private func buttonTapped() {
        // 使用防抖处理点击事件
        let debouncedAction = clickDebouncer.debounce { _ in
            print("Button tapped with debounce")
            // 这里可以放置需要执行的代码
        }
        debouncedAction(())
    }
    
    // 文本输入框内容变化事件处理
    @objc private func textFieldChanged(_ textField: UITextField) {
        // 使用防抖处理文本输入事件
        let debouncedAction = inputDebouncer.debounce { input in
            print("Text field value changed with debounce: \(input)")
            // 这里可以放置需要执行的代码
        }
        debouncedAction(textField.text ?? "")
    }
}

// 扩展 ExampleViewController 实现 UIScrollViewDelegate
extension ExampleViewController: UIScrollViewDelegate {
    // 滚动视图滚动事件处理
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 使用防抖处理滚动事件
        let debouncedAction = scrollDebouncer.debounce { _ in
            print("Scroll view did scroll with debounce")
            // 这里可以放置需要执行的代码
        }
        debouncedAction(())
    }
}

详细注释解释

  1. Debouncer 类:

    • 属性:
      • delay: 用于存储防抖时间间隔的属性,类型为 TimeInterval
      • queue: 用于执行防抖任务的队列,类型为 DispatchQueue
      • workItem: 当前待执行的任务,类型为 DispatchWorkItem?,用于在需要时取消任务。
      • stateQueue: 用于管理状态的队列,使用并发队列,并在修改状态时使用屏障(barrier)以确保线程安全。
      • state: 当前任务的状态,类型为枚举 State,初始状态为 idle(空闲)。
    • 初始化:
      • init(delay: TimeInterval, queue: DispatchQueue = .main): 初始化方法,设置防抖延迟时间和执行队列。
    • 方法:
      • debounce(action: @escaping (T) -> Void) -> (T) -> Void: 返回一个新的闭包,该闭包会在指定延迟时间后执行传入的任务闭包,同时取消之前的任务。使用 GCD 的屏障(barrier)特性来确保对状态的安全访问。
  2. ExampleViewController 类:

    • clickDebouncer: 用于处理点击事件的防抖实例,延迟时间为0.5秒。
    • inputDebouncer: 用于处理文本输入事件的防抖实例,延迟时间为0.3秒。
    • scrollDebouncer: 用于处理滚动事件的防抖实例,延迟时间为0.2秒