How to Zip More Than 4 Publishers

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

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)

Chaining + zipping multiple network requests using Swift and Combine

I think your attempt to obfuscate the code has removed the problem you were having. The following code compiles just fine:

func func1(arg1: String, arg2: String) -> AnyPublisher<String?, Error> { fatalError() }
func func2(arg1: String, arg2: String) -> AnyPublisher<String?, Error> { fatalError() }
func func3(arg1: String, arg2: String) -> AnyPublisher<String?, Error> { fatalError() }
func func4(arg1: String) -> AnyPublisher<String?, Error> { fatalError() }

func overallFunc(arg1: String, arg2: String, arg3: String, arg4: String) -> AnyPublisher<String?, Error> {
Publishers.Zip(
func1(arg1: arg1, arg2: arg2),
func2(arg1: arg3, arg2: arg4)
)
.flatMap { (response1, response2) in
func3(arg1: response1 ?? "", arg2: response2 ?? "")
}
.flatMap { response3 in
func4(arg1: response3 ?? "")
}
.eraseToAnyPublisher()
}

How to apply compactMap to two combined publishers

To be honest, I don't really think your first way is "ugly". I would just use it as it is.

If you really want to use compactMap on a publisher of pairs, well... compactMap wants you to transform the given element to nil to mean that you want this element to be removed. For mapping T? to T, this is easy, because the things we want to remove are exactly nil!

However, you want to map from (Int?, Int?) to (Int, Int), so we need to produce a nil value of (Int, Int)? when we find either of the values in the pair is nil, and return the pair, with its elements unwrapped, otherwise.

In short, you are trying to convert from a (Int?, Int?) to a (Int, Int)?

.compactMap { x, y -> (Int, Int)? in
guard let first = x, let second = y else { return nil }
return (first, second)
}

You can also do it in just one expression:

.compactMap { first, second in
first.flatMap { x in second.flatMap { y in (x, y) } }
}

There is no built in way to conveniently turn (Int?, Int?) into (Int, Int)?, but I have found this proposal that suggest this conversion should be a built in function.

You can write such a function yourself to make this look a bit prettier:

func unwrap<A, B>(pair: (A?, B?)) -> (A, B)? {
pair.0.flatMap { a -> (A, B)? in
pair.1.flatMap { b -> (A, B)? in
(a, b)
}
}
}

let sub = firstFeed
.combineLatest(secondFeed)
.compactMap(unwrap)
.sink { firstValue, secondValue in
print("we sunk with \(firstValue), \(secondValue)")
}

How to make a Publisher from array of Publishers?

As long as you don't insist too strongly on the "array" part, combineLatest or zip is correct for the Combine framework too. The difference between them is whether you want the overall value emitted each time to contain the oldest (zip) or the newest (combineLatest) contribution from each publisher.

Your example doesn't actually give enough information to tell which of those you want. To know which you want, you'd need to say what should happen when you say e.g.:

obs1.onNext(5) 
obs1.onNext(6)
obs2.onNext(10)

Be that as it may, Combine is Swift, so it uses a tuple, not an array, to express the totality of values. But if, as in your example, all the publishers have the same output and failure types, then an array can easily be substituted by mapping. See How to zip more than 4 publishers for an example turning zip into an array processor. Exactly the same technique works for combineLatest.

So, here's an actual working example; to make the example more general than yours, I used three publishers instead of two:

class ViewController: UIViewController {
let obs1 = PassthroughSubject<Int,Never>()
let obs2 = PassthroughSubject<Int,Never>()
let obs3 = PassthroughSubject<Int,Never>()
var storage = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()

let arr = [obs1, obs2, obs3]
let pub = arr.dropFirst().reduce(into: AnyPublisher(arr[0].map{[$0]})) {
res, ob in
res = res.combineLatest(ob) {
i1, i2 -> [Int] in
return i1 + [i2]
}.eraseToAnyPublisher()
}
pub.sink { print($0) }.store(in: &storage)
obs1.send(5)
obs2.send(10)
obs3.send(1) // [5, 10, 1]
obs1.send(12) // [12, 10, 1]
obs3.send(20) // [12, 10, 20]
}
}


Related Topics



Leave a reply



Submit