I can specify the scheduler as RunLoop.main
, but I could not find a native way to provide the associated RunLoop.Mode
mode to receive elements from a publisher.
Why do I need this: I'm updating a tableView cell from my publisher but the UI does not update if the user is scrolling, it then updates as soon as the user interaction or scroll stops. This is a known behaviour for scrollViews but I want my content to be displayed as soon as possible, and being able to specify the run loop tracking mode would fix this.
Combine API: I do not think the receive(on:options:)
method have any matching options to provide this. I think internally, if I call receive(on:RunLoop.main)
then RunLoop.main.perform { }
is called. This perform method can take the mode as parameter but this is not exposed to the Combine API.
Current Idea: To go around this I could do the perform action myself and not use the Combine API, so instead of doing this:
cancellable = stringFuture.receive(on: RunLoop.main) // I cannot specify the mode here
.sink { string in
cell.textLabel.text = string
}
I could do this:
cancellable = stringFuture.sink { string in
RunLoop.main.perform(inModes: [RunLoop.Mode.common]) { // I can specify it here
cell.textLabel.text = string
}
}
But this is not ideal.
Ideal Solution: I was wondering how could I wrap this into my own implementation of a publisher function to have something like this:
cancellable = stringFuture.receive(on: RunLoop.main, inMode: RunLoop.Mode.common)
.sink { string in
cell.textLabel.text = string
}
Were the API of this function could be something like this:
extension Publisher {
public func receive(on runLoop: RunLoop, inMode: RunLoop.Mode) -> AnyPublisher<Future.Output, Future.Failure> {
// How to implement this?
}
}
A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified. Each time you run your run loop, you specify (either explicitly or implicitly) a particular “mode” in which to run.
A RunLoop is a programmatic interface to objects that manage input sources, such as touches for an application. A RunLoop is created and managed by the system, who's also responsible for creating a RunLoop object for each thread object.
Actually what you've requested is custom Scheduler
, because RunLoop is a Scheduler
and running it in specific mode, instead of .default
, is just additional configuration of that scheduler.
I think that Apple will add such possibility in their RunLoop
scheduler in some of next updates, but for now the following simple custom scheduler that wraps RunLoop
works for me. Hope it would be helpful for you.
Usage:
.receive(on: MyScheduler(runLoop: RunLoop.main, modes: [RunLoop.Mode(rawValue: "myMode")]))
or
.delay(for: 10.0, scheduler: MyScheduler(runLoop: RunLoop.main, modes: [.common]))
Scheduler code:
struct MyScheduler: Scheduler {
var runLoop: RunLoop
var modes: [RunLoop.Mode] = [.default]
func schedule(after date: RunLoop.SchedulerTimeType, interval: RunLoop.SchedulerTimeType.Stride,
tolerance: RunLoop.SchedulerTimeType.Stride, options: Never?,
_ action: @escaping () -> Void) -> Cancellable {
let timer = Timer(fire: date.date, interval: interval.magnitude, repeats: true) { timer in
action()
}
for mode in modes {
runLoop.add(timer, forMode: mode)
}
return AnyCancellable {
timer.invalidate()
}
}
func schedule(after date: RunLoop.SchedulerTimeType, tolerance: RunLoop.SchedulerTimeType.Stride,
options: Never?, _ action: @escaping () -> Void) {
let timer = Timer(fire: date.date, interval: 0, repeats: false) { timer in
timer.invalidate()
action()
}
for mode in modes {
runLoop.add(timer, forMode: mode)
}
}
func schedule(options: Never?, _ action: @escaping () -> Void) {
runLoop.perform(inModes: modes, block: action)
}
var now: RunLoop.SchedulerTimeType { RunLoop.SchedulerTimeType(Date()) }
var minimumTolerance: RunLoop.SchedulerTimeType.Stride { RunLoop.SchedulerTimeType.Stride(0.1) }
typealias SchedulerTimeType = RunLoop.SchedulerTimeType
typealias SchedulerOptions = Never
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With