簡體   English   中英

子類化 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 function 放入計時器中。 我有一個手動調用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

如果你想讓一些操作延遲一定時間,我不會創建一個“隊列” isReady ,而是我會定義一個Operation直到那個時間過去(例如,五秒后) ). 這不僅消除了對兩個獨立“睡眠操作”的需要,而且完全消除了它們。

例如,

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)

它將延遲運行該任務(在這種情況下,只需登錄“某些任務”消息)五秒鍾。 如果你想重置定時器,只需在操作子類上調用該方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM