tl;dr:
If you want a tuple with previous
and current
values, without skipping the first stream event and without specifying an initial value - here is a solution (in this case the previous
value is an Optional
).
I realise there are already a lot of answers, but when trying them out it always seemed I want something slightly different.
I like the tuple approach, but I don't want to miss the first emission (skip(1)
) - I see some solutions with a startWith
value, but I wanted a solution where I don't need to provide an initial seed value, and where the tuple.previous
value is Optional
(to represent a "no previous value yet").
Here is what I came up with, it's a bit verbose (intentionally, to make it easier to read, feel free to shorten syntax). Also I had to constraint Element
to be Equatable
. Maybe there are better ways (please comment), but for now this is what works for me:
extension ObservableType where Element: Equatable {
/// Returns a tuple with `.previous` and `.current` value of the stream.
/// `.previous` is optional, because it might be the first stream value and no previous values exist yet
func withPrevious() -> Observable<(previous: Element?, current: Element)> {
scan((nil, nil)) { (accumulatedValue: (previous:Element?, current: Element?), newValue: Element) -> (previous: Element?, current: Element?) in
if accumulatedValue == (nil, nil) {
return (previous: nil, current: newValue)
} else {
return (previous: accumulatedValue.current, current: newValue)
}
}
.compactMap { (previous: Element?, current: Element?) -> (previous: Element?, current: Element)? in
guard let current else { return nil }
return (previous: previous, current: current)
}
}
}
Example usage:
Observable<Int>.from([1, 2, 3, 4])
.withPrevious()
.debug()
.subscribe()
.dispose()
Console output:
-> subscribed
-> Event next((previous: nil, current: 1))
-> Event next((previous: Optional(1), current: 2))
-> Event next((previous: Optional(2), current: 3))
-> Event next((previous: Optional(3), current: 4))
-> Event completed
-> isDisposed
As an extra - here is the above method but with a startWith
value if it makes sense for your use-case - so the .previous
value is no longer an Optional
. This is equivalent (but with longer syntax) of what others have already suggested in other answers.
/// Returns a tuple with `.previous` and `.current` value of the stream.
/// Expects a starting value which will be the `.previous` value of the first emission
func withPrevious(startWith: Element) -> Observable<(previous: Element, current: Element)> {
scan((startWith, startWith)) { (accumulatedValue: (previous: Element, current: Element), newValue: Element) -> (previous: Element, current: Element) in
if accumulatedValue == (startWith, startWith) {
return (previous: startWith, current: newValue)
} else {
return (previous: accumulatedValue.current, current: newValue)
}
}
}