How to get whole all array from observed publishers each time?

Viewed 123

In this example, each time sink fires, newValue contains Bool.

How to get [Bool] with states from all publishers each time it fires?

PS: .collect() before .sink makes it fire only once, that's not what I want, I don't want to stop observing

class SomePublisher: ObservableObject {
    @Published var state: Bool
    
    init(_ value: Bool) {
        state = value
    }
}

class Listener: ObservableObject {
    var bucket: [AnyCancellable] = []
    var publishers: [SomePublisher] = []
    
    func addPublishers() {
        publishers.append(.init(true))
        publishers.append(.init(true))
        publishers.append(.init(false))
        publishers.append(.init(false))
    }
    
    func subscribe() {
        Publishers.MergeMany(publishers.map{ $0.$state })
            .sink { newValue in
                print(newValue)
            }
            .store(in: &bucket)
    }
}

let listener = Listener()
listener.addPublishers()
listener.subscribe()

listener.publishers[1].state = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
    listener.publishers[0].state = false
}

Output:

true
true
false
false
false
false

Desired Output:

[true, true, false, false]
[true, false, false, false]
[false, false, false, false]
1 Answers

MergeMany is not the correct operator to use here. MergeMany simply subscribers to all of its upstreams and whenever any of the upstreams emits a value, it emits that value itself. So when you merge 4 Bool publishers, the downstream publisher will still have a Bool Output.

What you need is combineLatest. This operator emits the latest value of all of its upstreams whenever any of its upstreams emits a value.

The only downside of combineLatest is that it doesn't have a version which combines more than 4 upstream `Publishers, it can only handle 2-4 upstreams.

If your actual production code also only uses 4 publishers as your example, then the below code achieves your goals perfectly.

func subscribe() {
    Publishers.CombineLatest4(publishers[0].$state,
                              publishers[1].$state,
                              publishers[2].$state,
                              publishers[3].$state)
        .sink { newValue in
            print(newValue)
        }
        .store(in: &bucket)
}

If you have more than 4 upstreams, you can use a custom implementation of combineLatestMany, such as this one.

Related