添加睡眠期的OperationQueue子类


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()
}
}

我将requestDataOperation函数放入计时器中,每隔10秒一次。我有一个按钮可以手动调用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

如果您希望某个操作延迟一定时间,我不会创建"队列"类,而是只定义一个Operation,在该时间过去之前(例如,五秒后(,它不会是isReady。这样就不需要单独的"睡眠操作"。

例如,

class DelayedOperation: Operation {
private var enoughTimePassed = false
@Atomic private var timer: DispatchSourceTimer?
@Atomic 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()
startReadyTimer(with: timeInterval)
}
override func main() {
block?()
block = nil
}
override func cancel() {
// Just because operation is canceled, it doesn’t mean it always be immediately deallocated.
// So, let’s be careful and release our `block` and cancel the `timer`.
block = nil
timer = nil
super.cancel()
}
func startReadyTimer(with timeInterval: TimeInterval = 5) {
timer = DispatchSource.makeTimerSource()                        // GCD timer
timer?.setEventHandler { [weak self] in
guard let 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()
}
}

我用这个属性包装器消除了timerblock上的竞争

@propertyWrapper
struct Atomic<Value> {
private var value: Value
private let lock = NSLock()
init(wrappedValue: Value) {
value = wrappedValue
}
var wrappedValue: Value {
get { lock.synchronized { value } }
set { lock.synchronized { value = newValue } }
}
}
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}

不管怎样,定义了DelayedOperation,你就可以做一些类似的事情

logger.debug("creating operation")
let operation = DelayedOperation {
logger.debug("some task")
}
queue.addOperation(operation)

它会将运行该任务(在本例中,只是记录"某个任务"消息(延迟五秒钟。如果你想重置计时器,只需在操作子类上调用该方法:

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子类,该子类为isFinishedisExecuting等执行必要的KVO,因此您可以选择将上述isReady逻辑与现有的Operation子类相结合。

但这个想法是一个人可以完全失去";睡眠;异步模式的操作。如果你确实想要一个专用的睡眠操作,你仍然可以使用上面的模式(但要使它成为异步操作,而不是用sleep阻塞线程(。


尽管如此,如果我个人想取消网络请求,我不会将其集成到操作或操作队列中。我只会在我开始请求时退出:

weak var timer: Timer?
func debouncedRequest(in timeInterval: TimeInterval = 5) {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
// initiate request here
}
}

最新更新