Swift Combine: merge multiple publishers and emit `true` when any of them emits `true`

Viewed 1364

I am trying to build a publisher that emits true when any of 5 other publisher emits true. I've managed to build a working version but it feels very icky, with the CombineLatest4 + CombineLatest and especially all the $0.0 || $0.1 || $0.2 || $0.3 code.

I've tried Merge5, but that simply returns the value of the last value that was set it seems.

import Foundation
import Combine

class Test {
  @Published var one = false
  @Published var two = false
  @Published var three = false
  @Published var four = false
  @Published var five = false
}

let test = Test()

var anyTrue = Publishers.CombineLatest4(test.$one, test.$two, test.$three, test.$four)
  .map { $0.0 || $0.1 || $0.2 || $0.3 }
  .combineLatest(test.$five)
  .map { $0.0 || $0.1 }

anyTrue.sink {
  print($0)
}

test.three = true
test.one = false

Is there a cleaner less repetitive way to do this?

1 Answers

I have written this custom variadic combineLatest function that combines N publishers. Hopefully this is what you need:

func combineLatestN<P, T, E>(identity: T, reductionFunction: @escaping (T, T) -> T, publishers: P...) -> AnyPublisher<T, E> 
    where P: Publisher, P.Output == T, P.Failure == E {
    publishers.reduce(
        Publishers.Sequence<[T], E>(sequence: [identity]).eraseToAnyPublisher(), 
        { $0.combineLatest($1).map(reductionFunction).eraseToAnyPublisher() }
    )
}

The hard part was figuring out what the identity of the reduce should be. What publisher x satisfies x.combineLatest(y).map(f) == y for all y? One solution for x would be a publisher that publishes the identity of f once.

Usage:

let anyTrue = combineLatestN(
                identity: false, 
                reductionFunction: { $0 || $1 }, 
                publishers: test.$one, test.$two, test.$three, test.$four, test.$five)
Related