我有一個現有的 debouncer 實用程式,使用DispatchQueue. 它接受一個閉包并在達到時間閾值之前執行它。它可以像這樣使用:
let limiter = Debouncer(limit: 5)
var value = ""
func sendToServer() {
limiter.execute {
print("\(Date.now.timeIntervalSince1970): Fire! \(value)")
}
}
value.append("h")
sendToServer() // Waits until 5 seconds
value.append("e")
sendToServer() // Waits until 5 seconds
value.append("l")
sendToServer() // Waits until 5 seconds
value.append("l")
sendToServer() // Waits until 5 seconds
value.append("o")
sendToServer() // Waits until 5 seconds
print("\(Date.now.timeIntervalSince1970): Last operation called")
// 1635691696.482115: Last operation called
// 1635691701.859087: Fire! hello
請注意,它不是Fire!多次呼叫,而是在最后一次使用最后一個任務的值之后 5 秒。該Debouncer實體配置為無論呼叫多少次,都將佇列中的最后一個任務保留 5 秒。閉包被傳遞到execute(block:)方法中:
final class Debouncer {
private let limit: TimeInterval
private let queue: DispatchQueue
private var workItem: DispatchWorkItem?
private let syncQueue = DispatchQueue(label: "Debouncer", attributes: [])
init(limit: TimeInterval, queue: DispatchQueue = .main) {
self.limit = limit
self.queue = queue
}
@objc func execute(block: @escaping () -> Void) {
syncQueue.async { [weak self] in
if let workItem = self?.workItem {
workItem.cancel()
self?.workItem = nil
}
guard let queue = self?.queue, let limit = self?.limit else { return }
let workItem = DispatchWorkItem(block: block)
queue.asyncAfter(deadline: .now() limit, execute: workItem)
self?.workItem = workItem
}
}
}
如何將其轉換為并發操作,以便可以像下面這樣呼叫它:
let limit = Debouncer(limit: 5)
func sendToServer() {
await limiter.waitUntilFinished
print("\(Date.now.timeIntervalSince1970): Fire! \(value)")
}
sendToServer()
sendToServer()
sendToServer()
但是,這不會使任務去抖動,而是將它們掛起,直到下一個被呼叫。相反,它應該取消前一個任務并保持當前任務直到去抖動時間。這可以用Swift Concurrency來完成還是有更好的方法來做到這一點?
uj5u.com熱心網友回復:
任務可以使用isCancelled或checkCancellation,但是為了去抖動例程,您想等待一段時間,您可能只使用 的拋出再現Task.sleep(nanoseconds:),其檔案說:
如果任務在時間結束前被取消,則該函式拋出
CancellationError。
因此,這有效地消除了 2 秒的抖動。
var task: Task<(), Never>?
func debounced(_ string: String) {
task?.cancel()
task = Task {
do {
try await Task.sleep(nanoseconds: 2_000_000_000)
logger.log("result \(string)")
} catch {
logger.log("canceled \(string)")
}
}
}
(為什么 Apple 恢復到納秒級是我無法理解的。)
請注意, 的非拋出再現sleep(nanoseconds:)不會檢測取消,因此您必須使用此拋出再現。
uj5u.com熱心網友回復:
基于@Rob 的精彩回答,這里有一個使用actorand的示例Task:
actor Limiter {
enum Policy {
case throttle
case debounce
}
private let policy: Policy
private let duration: TimeInterval
private var task: Task<Void, Never>?
init(policy: Policy, duration: TimeInterval) {
self.policy = policy
self.duration = duration
}
nonisolated func callAsFunction(task: @escaping () async -> Void) {
Task {
switch policy {
case .throttle:
await throttle(task: task)
case .debounce:
await debounce(task: task)
}
}
}
private func throttle(task: @escaping () async -> Void) {
guard self.task?.isCancelled ?? true else { return }
Task {
await task()
}
self.task = Task {
try? await sleep()
self.task?.cancel()
self.task = nil
}
}
private func debounce(task: @escaping () async -> Void) {
self.task?.cancel()
self.task = Task {
do {
try await sleep()
guard !Task.isCancelled else { return }
await task()
} catch {
return
}
}
}
private func sleep() async throws {
try await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
}
}
測驗在通過時不一致,所以我認為我對任務觸發順序的假設是不正確的,但我認為示例是一個好的開始:
final class LimiterTests: XCTestCase {
func testThrottler() async throws {
// Given
let promise = expectation(description: "Ensure first task fired")
let throttler = Limiter(policy: .throttle, duration: 1)
var value = ""
var fulfillmentCount = 0
promise.expectedFulfillmentCount = 2
func sendToServer(_ input: String) {
throttler {
value = input
// Then
switch fulfillmentCount {
case 0:
XCTAssertEqual(value, "h")
case 1:
XCTAssertEqual(value, "hwor")
default:
XCTFail()
}
promise.fulfill()
fulfillmentCount = 1
}
}
// When
sendToServer("h")
sendToServer("e")
sendToServer("l")
sendToServer("l")
sendToServer("o")
await sleep(2)
sendToServer("wor")
sendToServer("ld")
wait(for: [promise], timeout: 10)
}
func testDebouncer() async throws {
// Given
let promise = expectation(description: "Ensure last task fired")
let limiter = Limiter(policy: .debounce, duration: 1)
var value = ""
var fulfillmentCount = 0
promise.expectedFulfillmentCount = 2
func sendToServer(_ input: String) {
limiter {
value = input
// Then
switch fulfillmentCount {
case 0:
XCTAssertEqual(value, "o")
case 1:
XCTAssertEqual(value, "old")
default:
XCTFail()
}
promise.fulfill()
fulfillmentCount = 1
}
}
// When
sendToServer("h")
sendToServer("e")
sendToServer("l")
sendToServer("l")
sendToServer("o")
await sleep(2)
sendToServer("wor")
sendToServer("ld")
wait(for: [promise], timeout: 10)
}
func testThrottler2() async throws {
// Given
let promise = expectation(description: "Ensure throttle before duration")
let throttler = Limiter(policy: .throttle, duration: 1)
var end = Date.now 1
promise.expectedFulfillmentCount = 2
func test() {
// Then
XCTAssertLessThan(.now, end)
promise.fulfill()
}
// When
throttler(task: test)
throttler(task: test)
throttler(task: test)
throttler(task: test)
throttler(task: test)
await sleep(2)
end = .now 1
throttler(task: test)
throttler(task: test)
throttler(task: test)
await sleep(2)
wait(for: [promise], timeout: 10)
}
func testDebouncer2() async throws {
// Given
let promise = expectation(description: "Ensure debounce after duration")
let debouncer = Limiter(policy: .debounce, duration: 1)
var end = Date.now 1
promise.expectedFulfillmentCount = 2
func test() {
// Then
XCTAssertGreaterThan(.now, end)
promise.fulfill()
}
// When
debouncer(task: test)
debouncer(task: test)
debouncer(task: test)
debouncer(task: test)
debouncer(task: test)
await sleep(2)
end = .now 1
debouncer(task: test)
debouncer(task: test)
debouncer(task: test)
await sleep(2)
wait(for: [promise], timeout: 10)
}
private func sleep(_ duration: TimeInterval) async {
await Task.sleep(UInt64(duration * 1_000_000_000))
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/345150.html
上一篇:無法執行URL請求SWIFT
