Combine框架里,关于时间控制大致有debounce、delay、measureInterval、throttle、timeout
下面我们分别介绍他们的区别和使用方法
值的我们注意的是Combine中的pipline是异步流,所以这些时间控制的Operator还是很强大的。
debounce
在某些情况下,我们可能会面临事件产生过于频繁的问题,比如用户输入的搜索关键字。如果我们在用户每次输入一个字符时都进行搜索,可能会导致不必要的网络请求。这时候,我们可以使用debounce
来确保只有在用户停止输入一段时间后才进行搜索。
let publisher = PassthroughSubject<Int, Never>()// 使用debounce操作符
let debounceCancellable = publisher.debounce(for: .seconds(3), scheduler: DispatchQueue.main).sink { value inprint("Debounce: \(value)")}publisher.send(1)
// 等待一段时间
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {publisher.send(2)
}
publisher.send(3)// 输出结果:
// Debounce: 2
时间轴
- 首先设置了时间窗口的时长,上图为3秒
- publisher每次发送一个新的数据,都会重新开启一个时间窗口,并取消之前的时间窗口
- 最后开启的时间窗口的时间结束后,如果没有新的数据,debounce把数据发送到下游
常见用例
- 搜索框输入:如上例所示,确保在用户停止输入一段时间后再进行搜索,以减少网络请求次数。
- 用户输入验证:在用户输入时,例如密码或用户名,使用debounce可以确保在用户停止输入后再执行验证,避免频繁的验证操作。
- UI界面更新:在某些情况下,当界面上的某个状态发生变化时,使用debounce可以避免过于频繁地更新UI,提高性能。
delay
在某些情况下,我们希望在接收到事件后等待一段时间再进行处理。例如,在用户点击一个按钮后,我们可能希望延迟一段时间再执行相应的操作,以提供更好的用户体验。
let publisher = PassthroughSubject<Int, Never>()// 使用debounce操作符
let debounceCancellable = publisher.delay(for: .seconds(3), scheduler: DispatchQueue.main).sink { value inprint("delay: \(value)")}publisher.send(1)
// 等待一段时间
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {publisher.send(2)
}
publisher.send(3)// 输出结果:
// delay: 1
// delay: 3
// delay: 2
时间轴
- 首先设置了时间窗口的时长,上图为3秒
- 只有一个时间窗口,它能够让pipline在收到publisher发送的数据后,等待一定的时长,然后再发送数据到下游
常见用例
- 搜索框输入:如上例所示,确保在用户停止输入一段时间后再进行搜索,以减少网络请求次数。
- 用户输入验证:在用户输入时,例如密码或用户名,使用debounce可以确保在用户停止输入后再执行验证,避免频繁的验证操作。
- UI界面更新:在某些情况下,当界面上的某个状态发生变化时,使用debounce可以避免过于频繁地更新UI,提高性能。
measureInterval
在某些情况下,我们可能需要知道两个事件之间经过了多长时间,以便根据时间间隔执行不同的操作。例如,在用户进行某个操作后,我们可能希望在一段时间内等待,然后执行一些额外的逻辑。这时候,measureInterval
就能派上用场。
private var cancellables: Set<AnyCancellable> = []
private var lastClickTime: Double = CFAbsoluteTimeGetCurrent()func simulateButtonClick() {Just(()).measureInterval(using: DispatchQueue.main).sink { timeInterval inprint(CFAbsoluteTimeGetCurrent() - lastClickTime)lastClickTime = CFAbsoluteTimeGetCurrent()}.store(in: &cancellables)
}simulateButtonClick()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {simulateButtonClick()
}// 输出结果:
// 0.0002
// 2.1025
measureInterval
它能够记录publisher发送数据的间隔时间
常见用例
- 点击事件时间间隔监测:在用户点击按钮或进行其他交互时,使用measureInterval可以监测两次事件之间的时间间隔,从而触发不同的操作。
- 用户活跃度追踪:记录用户活跃度并定期检查时间间隔,以执行相关的统计或分析。
- 定时任务:测量两次定时任务触发之间的时间,以确保它们按照预期的间隔执行。
timeout
在异步操作中,有时我们希望在一定时间内得到结果,如果超时就认为操作失败。例如,网络请求超过一定时间没有响应,我们可能希望取消请求并提示用户。
enum MyError: Error {case timeout
}let publisher = PassthroughSubject<Int, Never>()// 使用debounce操作符
let cancellable = publisher.setFailureType(to: MyError.self).timeout(.seconds(1), scheduler: DispatchQueue.main,customError: { MyError.timeout }).sink(receiveCompletion: { error inswitch error {case .finished:print("finished")case .failure(let failure):print("failure: \(failure)")}}, receiveValue: { value inprint("send: \(value)")})publisher.send(1)等待一段时间
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {publisher.send(2)
}// 输出结果:
// send: 1
// failure: timeout
时间轴
- 首先设置了时间窗口的时长,上图为1秒
- publisher.timeout() 的时候,seconds(1)这个超时时间就已经开始计时,而不是从publisher.send开始计时
- publisher每次发送一个新的数据,都会重新开启一个时间窗口(当然receiveCompletion执行了,整个pipline也就结束了)
常见用例
- 网络请求超时处理:设置网络请求的超时时间,确保及时处理请求的超时情况。
- 用户操作限时处理:在某些情况下,限定用户在一定时间内完成某个操作,超时则取消操作。
- 定时任务:对于定时任务,可以使用timeout确保任务在规定时间内完成。
throttle
在某些情况下,我们可能希望限制事件的传递速率,以降低处理的频率。例如,在用户输入搜索关键字时,我们可能不希望每次输入都触发搜索请求,而是希望等待用户停止输入一段时间后再触发搜索。这时 throttle
操作符就派上用场了。
let publisher = PassthroughSubject<Int, Never>()// 使用throttle操作符
let cancellable = publisher.throttle(for: .seconds(6), scheduler: DispatchQueue.main, latest: true).sink { value inprint("Throttle: \(value)")}// 发送元素
publisher.send(1)
publisher.send(2)
publisher.send(3)// 输出结果:
// Debounce: 3
Parameters:
- interval: 查找和发送最近的或第一个元素的时间间隔,用调度器的时间系统表示
- scheduler: 发布元素的调度器 (publisher.send()调用的线程要和scheduler保持一致,且不能async, 否则会执行多遍)
- latest: 布尔值,表示是否发布最新的元素。如果`false`, publisher会发出在间隔内接收到的第一个元素
常见用例
- 搜索输入限制:在用户输入搜索关键字时,使用 throttle 以限制搜索请求的频率,提高性能。
- UI界面更新:在某些情况下,限制 UI 界面更新的频率,以减少不必要的刷新。
- 防抖处理:与搜索输入类似,防抖处理是一种限制触发频率的常见用例。