子类化 OperationQueue 添加休眠期
Subclassing OperationQueue adding sleep period
import Foundation
class MyOperationQueue {
static let shared = MyOperationQueue()
private var queue: OperationQueue
init() {
self.queue = OperationQueue()
queue.name = "com.myqueue.name"
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .background
}
func requestDataOperation() {
queue.addOperation {
print("START NETWORK \(Date())")
NetworkService.shared.getData()
print("END NETWORK \(Date())")
}
}
func scheduleSleep() {
queue.cancelAllOperations()
queue.addOperation {
print("SLEEP START \(Date())")
Thread.sleep(forTimeInterval: 5)
print("SLEEP END \(Date())")
}
}
func cancelAll() {
queue.cancelAllOperations()
}
}
我每隔 10 秒将 requestDataOperation
函数放入计时器中。我有一个手动调用 scheduleSleep
的按钮。当我点击按钮时,我应该每 5 秒对请求进行去抖动。
但我得到这样的结果:
START NETWORK
END NETWORK
SLEEP START 2021-03-11 11:13:40 +0000
SLEEP END 2021-03-11 11:13:45 +0000
SLEEP START 2021-03-11 11:13:45 +0000
SLEEP END 2021-03-11 11:13:50 +0000
START NETWORK
END NETWORK
如何在我上次点击后增加 5 秒并将其组合在一起而不是将其拆分为两个操作?我调用 queue.cancelAllOperations
并开始新的休眠操作,但似乎不起作用。
预期结果:
START NETWORK
END NETWORK
SLEEP START 2021-03-11 11:13:40 +0000
// <- the second tap when 2 seconds passed away
SLEEP END 2021-03-11 11:13:47 +0000 // 2+5
START NETWORK
END NETWORK
如果您希望某些操作延迟一定时间,我不会创建“队列”class,而是定义一个 Operation
根本不会成为 isReady
直到那个时间过去(例如,五秒后)。这不仅消除了对两个单独的“睡眠操作”的需要,而且完全消除了它们。
例如,
class DelayedOperation: Operation {
@Atomic private var enoughTimePassed = false
private var timer: DispatchSourceTimer?
private var block: (() -> Void)?
override var isReady: Bool { enoughTimePassed && super.isReady } // this operation won't run until (a) enough time has passed; and (b) any dependencies or the like are satisfied
init(timeInterval: TimeInterval = 5, block: @escaping () -> Void) {
self.block = block
super.init()
resetTimer(for: timeInterval)
}
override func main() {
block?()
block = nil
}
func resetTimer(for timeInterval: TimeInterval = 5) {
timer = DispatchSource.makeTimerSource() // create GCD timer (eliminating reference to any prior timer will cancel that one)
timer?.setEventHandler { [weak self] in
guard let self = self else { return }
self.willChangeValue(forKey: #keyPath(isReady)) // make sure to do necessary `isReady` KVO notification
self.enoughTimePassed = true
self.didChangeValue(forKey: #keyPath(isReady))
}
timer?.schedule(deadline: .now() + timeInterval)
timer?.resume()
}
}
我正在使用以下 属性 包装器同步我与 enoughTimePassed
的交互,但您可以使用任何您想要的同步机制:
@propertyWrapper
struct Atomic<Value> {
private var value: Value
private var lock = NSLock()
init(wrappedValue: Value) {
value = wrappedValue
}
var wrappedValue: Value {
get { synchronized { value } }
set { synchronized { value = newValue } }
}
private func synchronized<T>(block: () throws -> T) rethrows -> T {
lock.lock()
defer { lock.unlock() }
return try block()
}
}
只需确保 isReady
是线程安全的。
无论如何,在定义 DelayedOperation
之后,您可以执行类似
的操作
logger.debug("creating operation")
let operation = DelayedOperation {
logger.debug("some task")
}
queue.addOperation(operation)
并且它会延迟 运行 该任务(在这种情况下,只需登录“某些任务”消息)五秒钟。如果你想重置计时器,只需在操作 subclass:
上调用该方法
operation.resetTimer()
比如我这里创建了任务,把它加入到队列中,每隔两秒重置它三次,最后一次重置后块实际上运行了五秒:
2021-09-30 01:13:12.727038-0700 MyApp[7882:228747] [ViewController] creating operation
2021-09-30 01:13:14.728953-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:16.728942-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:18.729079-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:23.731010-0700 MyApp[7882:228829] [ViewController] some task
现在,如果您正在使用网络请求操作,那么您可能已经实现了自己的异步 Operation
subclass,它为 isFinished
、isExecuting
,等等,所以你可以选择将上面的 isReady
逻辑与现有的 Operation
subclass.
结合起来
但我们的想法是可以用异步模式完全失去“睡眠”操作。如果你确实想要一个专门的睡眠操作,你仍然可以使用上面的模式(但是让它成为一个异步操作而不是用 sleep
阻塞线程)。
综上所述,如果我个人想要去抖网络请求,我不会将其集成到操作或操作队列中。我会在开始请求时进行去抖:
weak var timer: Timer?
func debouncedRequest(in timeInterval: TimeInterval = 5) {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
// initiate request here
}
}
import Foundation
class MyOperationQueue {
static let shared = MyOperationQueue()
private var queue: OperationQueue
init() {
self.queue = OperationQueue()
queue.name = "com.myqueue.name"
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .background
}
func requestDataOperation() {
queue.addOperation {
print("START NETWORK \(Date())")
NetworkService.shared.getData()
print("END NETWORK \(Date())")
}
}
func scheduleSleep() {
queue.cancelAllOperations()
queue.addOperation {
print("SLEEP START \(Date())")
Thread.sleep(forTimeInterval: 5)
print("SLEEP END \(Date())")
}
}
func cancelAll() {
queue.cancelAllOperations()
}
}
我每隔 10 秒将 requestDataOperation
函数放入计时器中。我有一个手动调用 scheduleSleep
的按钮。当我点击按钮时,我应该每 5 秒对请求进行去抖动。
但我得到这样的结果:
START NETWORK
END NETWORK
SLEEP START 2021-03-11 11:13:40 +0000
SLEEP END 2021-03-11 11:13:45 +0000
SLEEP START 2021-03-11 11:13:45 +0000
SLEEP END 2021-03-11 11:13:50 +0000
START NETWORK
END NETWORK
如何在我上次点击后增加 5 秒并将其组合在一起而不是将其拆分为两个操作?我调用 queue.cancelAllOperations
并开始新的休眠操作,但似乎不起作用。
预期结果:
START NETWORK
END NETWORK
SLEEP START 2021-03-11 11:13:40 +0000
// <- the second tap when 2 seconds passed away
SLEEP END 2021-03-11 11:13:47 +0000 // 2+5
START NETWORK
END NETWORK
如果您希望某些操作延迟一定时间,我不会创建“队列”class,而是定义一个 Operation
根本不会成为 isReady
直到那个时间过去(例如,五秒后)。这不仅消除了对两个单独的“睡眠操作”的需要,而且完全消除了它们。
例如,
class DelayedOperation: Operation {
@Atomic private var enoughTimePassed = false
private var timer: DispatchSourceTimer?
private var block: (() -> Void)?
override var isReady: Bool { enoughTimePassed && super.isReady } // this operation won't run until (a) enough time has passed; and (b) any dependencies or the like are satisfied
init(timeInterval: TimeInterval = 5, block: @escaping () -> Void) {
self.block = block
super.init()
resetTimer(for: timeInterval)
}
override func main() {
block?()
block = nil
}
func resetTimer(for timeInterval: TimeInterval = 5) {
timer = DispatchSource.makeTimerSource() // create GCD timer (eliminating reference to any prior timer will cancel that one)
timer?.setEventHandler { [weak self] in
guard let self = self else { return }
self.willChangeValue(forKey: #keyPath(isReady)) // make sure to do necessary `isReady` KVO notification
self.enoughTimePassed = true
self.didChangeValue(forKey: #keyPath(isReady))
}
timer?.schedule(deadline: .now() + timeInterval)
timer?.resume()
}
}
我正在使用以下 属性 包装器同步我与 enoughTimePassed
的交互,但您可以使用任何您想要的同步机制:
@propertyWrapper
struct Atomic<Value> {
private var value: Value
private var lock = NSLock()
init(wrappedValue: Value) {
value = wrappedValue
}
var wrappedValue: Value {
get { synchronized { value } }
set { synchronized { value = newValue } }
}
private func synchronized<T>(block: () throws -> T) rethrows -> T {
lock.lock()
defer { lock.unlock() }
return try block()
}
}
只需确保 isReady
是线程安全的。
无论如何,在定义 DelayedOperation
之后,您可以执行类似
logger.debug("creating operation")
let operation = DelayedOperation {
logger.debug("some task")
}
queue.addOperation(operation)
并且它会延迟 运行 该任务(在这种情况下,只需登录“某些任务”消息)五秒钟。如果你想重置计时器,只需在操作 subclass:
上调用该方法operation.resetTimer()
比如我这里创建了任务,把它加入到队列中,每隔两秒重置它三次,最后一次重置后块实际上运行了五秒:
2021-09-30 01:13:12.727038-0700 MyApp[7882:228747] [ViewController] creating operation
2021-09-30 01:13:14.728953-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:16.728942-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:18.729079-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:23.731010-0700 MyApp[7882:228829] [ViewController] some task
现在,如果您正在使用网络请求操作,那么您可能已经实现了自己的异步 Operation
subclass,它为 isFinished
、isExecuting
,等等,所以你可以选择将上面的 isReady
逻辑与现有的 Operation
subclass.
但我们的想法是可以用异步模式完全失去“睡眠”操作。如果你确实想要一个专门的睡眠操作,你仍然可以使用上面的模式(但是让它成为一个异步操作而不是用 sleep
阻塞线程)。
综上所述,如果我个人想要去抖网络请求,我不会将其集成到操作或操作队列中。我会在开始请求时进行去抖:
weak var timer: Timer?
func debouncedRequest(in timeInterval: TimeInterval = 5) {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
// initiate request here
}
}