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
Using Foreach with a an Array of Bindings (Swiftui)
Dispatchqueue:Cannot Be Called with Ascopy = No on Non-Main Thread
"Nsurl" Is Not Implicitly Convertible to "Url"; Did You Mean to Use "As" to Explicitly Convert
Add Text Label and Button to Dynamic Tableview Cell Programmatically with Swift
Swiftui - How to Set the Title of a Navigationview to Large Title (Or Small)
Uicollectionviewcell Register Class Fails, But Register Nib Works
Swift: How to Open File with Associated Application
Using a Mtltexture as the Environment Map of a Scnscene
Optional Chaining with Swift Strings
Changing Tab Bar Font in Swift
Swiftui: How to Iterate Over an Array of Bindable Objects
How to Make a Uiview Focusable Using the Focus Engine on Apple Tv
Using Cocoa Nssavepanel in Sandbox Causes Assertion Failure
Delegate Using Container View in Swift
Ios-Charts Library: X-Axis Labels Without Backing Data Not Showing
Why Does an Optional in Fast Enumeration Cause an Infinite Loop