Debounce
和 Throttle
是两种常用的操作符,用于控制数据流的频率和处理延迟。但它们的实现方式略有不同。理解这些差异对于在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
for
: 这是一个表示时间间隔的参数,指定了在没有新数据到达时等待的时间长度。可以是DispatchTimeInterval
类型,如.seconds(1)表示1秒,.milliseconds(500)表示500毫秒等。scheduler
: 这是一个调度器参数,用于指定在哪个调度器上执行等待和发送操作。通常可以使用DispatchQueue.main
来在主队列上执行操作,也可以用RunLoop.main
,也可以使用其他自定义的调度器。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
for
: 这是一个表示时间间隔的参数,指定了每隔多长时间发送一次数据。可以是DispatchTimeInterval
类型,如.seconds(1)表示1秒,.milliseconds(500)表示500毫秒等。scheduler
: 这是一个调度器参数,用于指定在哪个调度器上执行等待和发送操作。通常可以使用DispatchQueue.main
来在主队列上执行操作,也可以用RunLoop.main
,也可以使用其他自定义的调度器。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)。
写在最后
理解Combine
中debounce
和throttle
的区别对于有效的事件处理和数据流控制至关重要。
Debounce
操作符用于限制数据流的频率,只有在指定的时间间隔内没有新数据到达时,才会将最后一个数据发送出去。
Throttle
操作符用于控制数据流的速率,只有在指定的时间间隔内才会发送数据,忽略掉间隔内的其他数据。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。