Swift 函数防抖与节流

572 阅读3分钟

在 Swift 中实现防抖(Debounce)和节流(Throttle)功能,可以通过封装公共函数来实现。下面是详细的示例及注释,展示如何使用防抖和节流处理公有函数的调用。

通过在实现中引入更多的设计模式和高级特性,比如使用策略模式、闭包捕获、泛型约束、类型擦除、协议组合等。以下是实现防抖(Debounce)和节流(Throttle)功能的实现:

防抖(Debounce)示例

import Foundation
import Combine

// MARK: - Debounce Strategy

protocol DebounceStrategy {
    associatedtype Value
    func debounce(value: Value, action: @escaping (Value) -> Void)
}

final class CombineDebounceStrategy<Value>: DebounceStrategy {
    private var subject: PassthroughSubject<Value, Never>
    private var cancellable: AnyCancellable?
    private let interval: TimeInterval
    private let queue: DispatchQueue
    
    init(interval: TimeInterval, queue: DispatchQueue = .main) {
        self.interval = interval
        self.queue = queue
        self.subject = PassthroughSubject<Value, Never>()
    }
    
    func debounce(value: Value, action: @escaping (Value) -> Void) {
        cancellable?.cancel()
        cancellable = subject
            .debounce(for: .seconds(interval), scheduler: queue)
            .sink { value in
                action(value)
            }
        subject.send(value)
    }
}

// MARK: - Debouncer

class Debouncer<Value> {
    private let strategy: AnyDebounceStrategy<Value>
    
    init<Strategy: DebounceStrategy>(strategy: Strategy) where Strategy.Value == Value {
        self.strategy = AnyDebounceStrategy(strategy)
    }
    
    func callAsFunction(_ value: Value, action: @escaping (Value) -> Void) {
        strategy.debounce(value: value, action: action)
    }
}

// MARK: - Type Erasure

final class AnyDebounceStrategy<Value>: DebounceStrategy {
    private let _debounce: (Value, @escaping (Value) -> Void) -> Void
    
    init<Strategy: DebounceStrategy>(_ strategy: Strategy) where Strategy.Value == Value {
        _debounce = strategy.debounce
    }
    
    func debounce(value: Value, action: @escaping (Value) -> Void) {
        _debounce(value, action)
    }
}

// MARK: - Example Usage

final class Example {
    private let debouncer: Debouncer<String>
    
    init() {
        debouncer = Debouncer(strategy: CombineDebounceStrategy(interval: 1.0))
    }
    
    public func userInputChanged(text: String) {
        debouncer(text) { [weak self] debouncedText in
            self?.handleUserInput(debouncedText)
        }
    }
    
    private func handleUserInput(_ text: String) {
        print("Handling user input: \(text)")
    }
}

let example = Example()
example.userInputChanged(text: "Hello")
example.userInputChanged(text: "Hello World")
example.userInputChanged(text: "Hello Swift")

节流(Throttle)示例

import Foundation
import Combine

// MARK: - Throttle Strategy

protocol ThrottleStrategy {
    associatedtype Value
    func throttle(value: Value, action: @escaping (Value) -> Void)
}

final class CombineThrottleStrategy<Value>: ThrottleStrategy {
    private var subject: PassthroughSubject<Value, Never>
    private var cancellable: AnyCancellable?
    private let interval: TimeInterval
    private let queue: DispatchQueue
    
    init(interval: TimeInterval, queue: DispatchQueue = .main) {
        self.interval = interval
        self.queue = queue
        self.subject = PassthroughSubject<Value, Never>()
    }
    
    func throttle(value: Value, action: @escaping (Value) -> Void) {
        cancellable?.cancel()
        cancellable = subject
            .throttle(for: .seconds(interval), scheduler: queue, latest: true)
            .sink { value in
                action(value)
            }
        subject.send(value)
    }
}

// MARK: - Throttler

class Throttler<Value> {
    private let strategy: AnyThrottleStrategy<Value>
    
    init<Strategy: ThrottleStrategy>(strategy: Strategy) where Strategy.Value == Value {
        self.strategy = AnyThrottleStrategy(strategy)
    }
    
    func callAsFunction(_ value: Value, action: @escaping (Value) -> Void) {
        strategy.throttle(value: value, action: action)
    }
}

// MARK: - Type Erasure

final class AnyThrottleStrategy<Value>: ThrottleStrategy {
    private let _throttle: (Value, @escaping (Value) -> Void) -> Void
    
    init<Strategy: ThrottleStrategy>(_ strategy: Strategy) where Strategy.Value == Value {
        _throttle = strategy.throttle
    }
    
    func throttle(value: Value, action: @escaping (Value) -> Void) {
        _throttle(value, action)
    }
}

// MARK: - Example Usage

final class Example {
    private let throttler: Throttler<CGPoint>
    
    init() {
        throttler = Throttler(strategy: CombineThrottleStrategy(interval: 1.0))
    }
    
    public func userDidScroll(position: CGPoint) {
        throttler(position) { [weak self] throttledPosition in
            self?.handleScroll(throttledPosition)
        }
    }
    
    private func handleScroll(_ position: CGPoint) {
        print("Handling scroll event at position: \(position)")
    }
}

let example = Example()
example.userDidScroll(position: CGPoint(x: 100, y: 200))
example.userDidScroll(position: CGPoint(x: 150, y: 250))
example.userDidScroll(position: CGPoint(x: 200, y: 300))

注释

防抖(Debounce)示例

  1. 策略模式:通过定义 DebounceStrategy 协议和具体实现类 CombineDebounceStrategy,使用策略模式来实现不同的防抖策略。
  2. 类型擦除:使用 AnyDebounceStrategy 进行类型擦除,以隐藏具体的策略实现细节。
  3. 泛型约束:使用泛型约束来确保 Debouncer 可以接受任何实现了 DebounceStrategy 的策略。
  4. 闭包捕获:在 callAsFunction 方法中,捕获闭包以确保防抖处理能够正确执行。

节流(Throttle)示例

  1. 策略模式:通过定义 ThrottleStrategy 协议和具体实现类 CombineThrottleStrategy,使用策略模式来实现不同的节流策略。
  2. 类型擦除:使用 AnyThrottleStrategy 进行类型擦除,以隐藏具体的策略实现细节。
  3. 泛型约束:使用泛型约束来确保 Throttler 可以接受任何实现了 ThrottleStrategy 的策略。
  4. 闭包捕获:在 callAsFunction 方法中,捕获闭包以确保节流处理能够正确执行。

通过这些设计封装,具备更高的灵活性和可扩展性。