Search code examples
iosswiftreactive-programmingcombinepublisher

How to zip two publishers but to get newest values instead of default oldest behavior of zip?


I need to build a Combine pipeline using Swift where 2 publishers are involved, pubA (A PassThroughSubject) generates values frequently and once pubB (another PassThroughSubject) generates a value, I need to use the latest value from pubA to trigger an event. The only strict condition is - the trigger should happen only on a new value of pubB (the driving publisher) and the pipeline should take most newest published value from pubA (should skip old values). Let me try to demonstrate with example:

pubA => 
1               
          2               
                 3               
                                   4
                                             5 
pubB =>                  true                     false       true
Expected Output:         (true, 3)                (false, 5)  (true, 5)

This seemed to be good for Zip initially but zip pops old value not newest, i.e the output becomes (true, 1), (false, 2). And using combineLatest triggers event whenever there is a value from any of the publishers & breaks the dependency to trigger only on pubB’s new value, so the output becomes (true, 3), (true, 4), (true, 5), (false, 5)

Appreciate any pointers.

Edit Notes:

  1. The expectation is to get pair of values.
  2. pubB is driving publisher, for each value from pubB there will be an event by using newest value from pubA.
  3. If pubA hasn't produced any value, it will wait until pubA produces a value... as we need a pair.
  4. If pubB produces more values than pubA, the newest pubA value will be paired with newest pubB value.

Solution

  • Here is the final code that I came up with to match my exact requirement. I was reluctant to write a custom pub-sub or port combineLatestFrom from RxSwift to Combine. Thanks to @matt for directing me to the right approach he answered here: Swift Combine operator with same functionality like `withLatestFrom` in the RxSwift Framework

    import Combine
    import Foundation
    
    let pub1 = PassthroughSubject<Int, Never>()
    let pub2 = PassthroughSubject<Bool, Never>()
    var subscriptions = Set<AnyCancellable>()
    
    pub2.map { value in (unique: UUID(), value: value) }
        .combineLatest(pub1)
        .removeDuplicates {
            return $0.0.unique == $1.0.unique
        }
        .map { (tuple) in
            return (tuple.0.1, tuple.1)
        }
        .sink { event in
            print(event)
        }
        .store(in: &subscriptions)
    
    pub1.send(1)
    pub1.send(2)
    pub1.send(2)
    pub1.send(3)
    pub2.send(true)
    pub1.send(4)
    pub1.send(5)
    pub2.send(false)
    pub2.send(true)
    

    And the output is: (true, 3)
    (false, 5)
    (true, 5)