I'm interested in getting notified when the currentMode property of the RunLoop class changes, more specifically, I'm interested in getting an event when the mode is entering .tracking
state.
I've tried two different approaches:
This one simply doesn't work, what could be wrong with it?:
import Foundation
public final class RunLoopTracker {
private let runLoop: RunLoop
private var observation: NSKeyValueObservation?
public init(runLoop: RunLoop) {
self.runLoop = runLoop
}
public func attach() {
observation = runLoop.observe(\.currentMode) { runLoop, change in
print(change)
}
}
}
This one works, but fires only once. I'd like to get the block executed each time the RunLoop
enters the specific mode:
import Foundation
public final class RunLoopTracker2 {
private let runLoop: RunLoop
private var observation: NSKeyValueObservation?
public init(runLoop: RunLoop) {
self.runLoop = runLoop
}
public func attach() {
runLoop.perform(inModes: [.tracking]) {
print("Entering the tracking mode, send notification")
}
}
}
What could be the solution to these two problems or a different approach to track RunLoop.currentMode
changes?
I finished with the following solution:
import Foundation
import Combine
final class RunLoopModeTracker: ObservableObject {
@Published var isTracking = false
private let runLoop: RunLoop
private var taskSet = false
public init(runLoop: RunLoop = .main) {
self.runLoop = runLoop
submitTaskForTrackingMode()
}
private func submitTaskForTrackingMode() {
if !taskSet {
runLoop.perform(inModes: [.tracking]) { [weak self] in
self?.notify()
}
taskSet = true
}
}
private func notify() {
isTracking = true
taskSet = false
submitTaskForDefaultMode()
}
private func submitTaskForDefaultMode() {
if !taskSet {
runLoop.perform(inModes: [.default]) { [weak self] in
guard let self = self else {return}
self.isTracking = false
self.submitTaskForTrackingMode()
}
}
}
}
And then at call site I just use it like this:
@StateObject private var runLoopTracker = RunLoopModeTracker()
/// ...
.onChange(of: runLoopTracker.isTracking) { isTracking
/// isTracking value has changed to true
}
Essentially, the idea is to add the task for both the default
and the tracking
modes and once the runloop enters any of those, update the status correspondingly.