pwightman
pwightman

Reputation: 794

Custom change operator using Combine

I'm looking to create a Combine publisher/subscriber/subscription that behaves like this:

struct Change<Value> {
  let new: Value
  let previous: Value?
}

let pub = PassthroughSubject<Int, Never>()

let cancellable = pub
  .change()
  .sink { (change: Change<Int>) -> Void in
    print(change)
  }

pub.send(1) // prints Change(new: 1, previous: nil)
pub.send(2) // prints Change(new: 2, previous: 1)
pub.send(3) // prints Change(new: 3, previous: 2)

Having trouble coming up with the right implementation. I've made my own Publisher/Subscription to wrap external API calls and the sort, but can't come up with the right combination when some state needs to be retained, like the previous value in this example (I think this means you need a custom Subscriber?)

An alternate syntax with the same semantics would also be acceptable, if for some reason the .change() syntax is unworkable.

Upvotes: 2

Views: 865

Answers (1)

rob mayoff
rob mayoff

Reputation: 385650

You can build the change operator out of the scan and map operators like this:

struct Change<Value> {
    var old: Value?
    var new: Value
}

extension Publisher {
    func change() -> Publishers.Map<Publishers.Scan<Self, (Optional<Self.Output>, Optional<Self.Output>)>, Change<Self.Output>> {
        return self
            .scan((Output?.none, Output?.none)) { (state, new) in
                (state.1, .some(new))
            }
            .map { (old, new) in
                Change(old: old, new: new!)
            }
    }
}

Demo:

let pub = PassthroughSubject<Int, Never>()
let ticket = pub
    .change()
    .sink { print($0) }

pub.send(1)
// Output: Change<Int>(old: nil, new: 1)

pub.send(2)
// Output: Change<Int>(old: Optional(1), new: 2)

pub.send(3)
// Output: Change<Int>(old: Optional(2), new: 3)

Upvotes: 5

Related Questions