Swift Combine — Debounce和Throttle的理解与使用

DebounceThrottle 是两种常用的操作符,用于控制数据流的频率和处理延迟。但它们的实现方式略有不同。理解这些差异对于在Combine代码中做出正确选择至关重要。

Debounce

Debounce 操作符用于限制数据流的频率,只有在指定的时间间隔内没有新数据到达时,才会将最后一个数据发送出去。

public func debounce<S>(for dueTime: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.Debounce<Self, S> where S : Scheduler
  1. for: 这是一个表示时间间隔的参数,指定了在没有新数据到达时等待的时间长度。可以是DispatchTimeInterval类型,如.seconds(1)表示1秒,.milliseconds(500)表示500毫秒等。
  2. scheduler: 这是一个调度器参数,用于指定在哪个调度器上执行等待和发送操作。通常可以使用DispatchQueue.main来在主队列上执行操作,也可以用RunLoop.main,也可以使用其他自定义的调度器。
  3. options: 这是一个可选的参数,用于指定额外的选项。基本不用,直接忽略。
class DebounceViewModel: ObservableObject {

  let publisher = PassthroughSubject<String, Never>()
  private var cancellable = Set<AnyCancellable>()
  @Published var outputArray: [String] = []

  init() {
    setUpSubscription()
  }

  func setUpSubscription() {
    publisher
      .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
      .sink { [weak self] value in
        print("Received value: \(value)")
        self?.outputArray.append(value)
      }
      .store(in: &cancellable)
  }

  func sendMessage(_ text: String) {
    publisher.send(text)
  }
}

上面代码中,代码中添加了debounce方法,并设置了间隔时间为0.5秒。

执行原理

  • 当接收到新的值时,debounce启动一个定时器。
  • 如果在定时器到期前收到其他值,则复位定时器。
  • 在没有新的输入的情况下,计时器完成后才会发出最新的值。

SwiftUI界面中,模拟了用户连续输入的情况,比如下面代码中的viewModelSendMessage方法。

func viewModelSendMessage() {
    // 发送1,开始计时。
    viewModel.sendMessage("1")

    // 0.25秒后发送2,与上次发送间隔未超过0.5秒,停止上次计时,并且重新开始计时,记录最新值为2.
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
      viewModel.sendMessage("2")
    }

    // 0.5秒后发送3,与上次发送间隔未超过0.5秒,停止上次计时,并且重新开始计时,记录最新值为3.
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
      viewModel.sendMessage("3")
    }

    // 0.75秒后发送4,与上次发送间隔未超过0.5秒,停止上次计时,并且重新开始计时,记录最新值为4.
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
      viewModel.sendMessage("4")
    }

    // 1.3秒后发送5,与上次发送间隔超过0.5秒,所以4已经在1.25秒的时候发出去了,并订阅者收到。此时发送5并开始计时。
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.3) {
      viewModel.sendMessage("5")
    }
    // 2秒后发送6,与上次发送间隔超过0.5秒,所以5已经在1,8秒的时候发出去了,并订阅者收到。此时发送6并开始计时。发送6后没有再发送任何数据了,所以过0.5秒后,订阅者收到6.
    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
      viewModel.sendMessage("6")
    }
  }

代码注释中已经备注了执行原理。
在SwiftUI中,我们用一个List显示打印出来的结果,代码如下:

  @StateObject private var viewModel = DebounceViewModel()

  var body: some View {
    VStack {
      Button("Send messages") {
        viewModelSendMessage()
      }
      .buttonStyle(BorderedProminentButtonStyle())

      List(viewModel.outputArray, id: \.self) { value in
        Text(value)
          .font(.title)
          .frame(maxWidth: .infinity, alignment: .leading)
      }
      .listStyle(PlainListStyle())
    }
  }

执行效果如下:
在这里插入图片描述

使用场景

  • 当你想对用户输入或数据更改做出反应,但不想处理每个中间值时,Debounce特别有用。
  • 常见的用例包括搜索栏、文本输入字段或自动建议,您希望在开始搜索之前等待用户暂停输入。

Throttle

Throttle 操作符用于控制数据流的速率,只有在指定的时间间隔内才会发送数据,忽略掉间隔内的其他数据。

public func throttle<S>(for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: Bool) -> Publishers.Throttle<Self, S> where S : Scheduler
  1. for: 这是一个表示时间间隔的参数,指定了每隔多长时间发送一次数据。可以是DispatchTimeInterval类型,如.seconds(1)表示1秒,.milliseconds(500)表示500毫秒等。
  2. scheduler: 这是一个调度器参数,用于指定在哪个调度器上执行等待和发送操作。通常可以使用DispatchQueue.main来在主队列上执行操作,也可以用RunLoop.main,也可以使用其他自定义的调度器。
  3. latest: 这是一个布尔值参数,用于指定是否只发送最新的数据。如果设置为true,则只发送最新的数据,忽略掉间隔内的其他数据;如果设置为false,则会发送间隔内的第一个数据。
class ThrottleDemoViewModel: ObservableObject {

  let publisher = PassthroughSubject<String, Never>()
  private var cancellable = Set<AnyCancellable>()
  @Published var outputArray: [String] = []

  init() {
    setUpSubscription()
  }

  func setUpSubscription() {
    publisher
      .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
      .sink { [weak self] value in
        print("Received value: \(value)")
        self?.outputArray.append(value)
      }
      .store(in: &cancellable)
  }

  func sendMessage(_ text: String) {
    publisher.send(text)
  }
}

上面代码中设置间隔时间为0.5秒,期间内发送最后一次的值。

执行原理

  • 当接收到新值时,启动计时器并允许该值通过。
  • 在计时器持续时间内收到的任何后续值都将被忽略。
  • 计时器到期后,该过程重复,允许下一个值通过并开始一个新的计时器。
struct ThrottleDemo: View {
  @StateObject private var viewModel = ThrottleDemoViewModel()

  var body: some View {
    VStack {
      Button("Send messages") {
        viewModelSendMessage()
      }
      .buttonStyle(BorderedProminentButtonStyle())

      List(viewModel.outputArray, id: \.self) { value in
        Text(value)
          .font(.title)
          .frame(maxWidth: .infinity, alignment: .leading)
      }
      .listStyle(PlainListStyle())
    }
  }

  func viewModelSendMessage() {
    // 1
    viewModel.sendMessage("1")
    // 2
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
      viewModel.sendMessage("2")
    }
    // 3
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
      viewModel.sendMessage("3")
    }
    // 4
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
      viewModel.sendMessage("4")
    }
    // 5
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
      viewModel.sendMessage("5")
    }
    // 6
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.3) {
      viewModel.sendMessage("6")
    }
  }
}

viewModelSendMessage方法中,发送1后,直接让1通过,并开始计时:
0~0.5秒内:发送了2和3。最终3通过(如果latest为false,2通过)。
0.5~1.0秒内:发送了4和5。最终5通过(如果latest为false,4通过)。
1.0~1.5秒内:发送了6。最终6通过。

执行效果如下(latest为true):
在这里插入图片描述
执行效果如下(latest为false):
在这里插入图片描述

使用场景

  • 当你想要强制一个一致的更新速度,或者当你想要防止超载的下游系统与过多的数据。
  • 它通常用于滚动事件或处理UI组件中的用户交互等场景(比如防止连续点击Button)。

写在最后

理解Combinedebouncethrottle的区别对于有效的事件处理和数据流控制至关重要。
Debounce 操作符用于限制数据流的频率,只有在指定的时间间隔内没有新数据到达时,才会将最后一个数据发送出去。
Throttle 操作符用于控制数据流的速率,只有在指定的时间间隔内才会发送数据,忽略掉间隔内的其他数据。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值