Reputation: 3471
What's the best way, to delay a moment when a publisher sends some data in Swift Combine? Let's assume following situation:
private var publisher: PassthroughSubject<Progress, Error>
// closure called every second:
startWithProgress() { [weak self] progress in
self.publisher.send(.init(progress: progress))
// How to call this 0.5 second after the above `send`:
self.publisher.send(.init(progress: progress + 0.5))
}
I checked Delay
API, but it seems I'd need to create another publisher to make use of it, which is suboptimal in my case. I also checked throttle
and debounce
, but those also don't allow me to send 2 updates one after another, with a given delay between them.
Upvotes: 0
Views: 3945
Reputation: 27
I haven't tested this, it might make more sense to use a timer. You will need to store cancellable in an instance variable so it doesn't disappear when the function returns.
var cancellable = self.publisher
.delay(for: .seconds(0.5), scheduler: RunLoop.main )
.sink(receiveCompletion:{ _ in
// do something with the error or completion
}, receiveValue:{ [unowned self] progress in
// you don't show any code that actually updates progress, without that this will just keep sending the initial value + 0.5.
self.publisher.send(.init(progress: progress + 0.5))
})
self.publisher.send(.init(progress: progress))
Upvotes: 1
Reputation: 49620
There's a difference between delaying the act of sending the values - which you can do with DispatchQueue.asyncAfter
- and creating a combine pipeline that delays upstream values.
You haven't specified any details about what you are actually trying to accomplish, so it's hard to give a definitive answer.
If I was to generalize, it looks like that you want a pipeline that for each upstream value it emits the value, then emits the value + 0.5
again, but delayed. This could be done like below, as an example:
let duplicateAndDelay = publisher
.flatMap { [($0, 0), ($0 + 0.5, 0.5)].publisher } // duplicate
.flatMap { (progress, delay) in
Just(progress)
.delay(for: .seconds(delay), scheduler: RunLoop.main) // delay
}
Then you can just send
once:
startWithProgress() { [weak self] progress in
self?.publisher.send(progress)
}
and return the duplicateAndDelay
publisher, instead of the publisher
publisher, to be subscribed to.
Upvotes: 3
Reputation: 2671
Judging from what you shared, I would not use Combine. DispatchQueue.asyncAfter(deadline:execute:)
seems to be enough.
That being said if you must, you can use Publisher.delay(for:tolerance:scheduler:options:)
:
let subject = PassthroughSubject<Progress, Error>()
subject.delay(for: .seconds(5), scheduler: RunLoop.main)
.replaceError(with: .init())
.sink { progress in
self.publisher.send(.init(progress: progress + 0.5))
}
.store(in: &cancellables)
or even Publisher.publish(every:tolerance:on:in:options:)
Timer
.publish(every: 5, on: .main, in: .default)
.autoconnect()
.first()
.sink { _ in
self.publisher.send(.init(progress: progress + 0.5))
}
or, if it makes sense in your case, have progress
in a @Published
variable and use it to start a pipeline
Upvotes: 2