Assume the two following publishers:
var firstFeed = PassthroughSubject<Int?, Never>()
var secondFeed = PassthroughSubject<Int?, Never>()
How do I create a subscription where the sink is called when the two feed provide two none-nil values. This is what I have so far, but it seems ugly.
let sub = firstFeed
.compactMap{$0}
.combineLatest(secondFeed.compactMap({$0}))
.sink { firstValue, secondValue in
print("we sunk with \(firstValue), \(secondValue)")
}
With the following stream of data:
firstFeed.send(1)
secondFeed.send(nil)
secondFeed.send(2)
firstFeed.send(nil)
firstFeed.send(3)
firstFeed.send(nil)
secondFeed.send(nil)
firstFeed.send(nil)
secondFeed.send(nil)
secondFeed.send(4)
firstFeed.send(nil)
secondFeed.send(nil)
firstFeed.send(5)
secondFeed.send(6)
secondFeed.send(nil)
firstFeed.send(nil)
firstFeed.send(7)
firstFeed.send(nil)
secondFeed.send(8)
I get this output:
we sunk with 1, 2
we sunk with 3, 2
we sunk with 3, 4
we sunk with 5, 4
we sunk with 5, 6
we sunk with 7, 6
we sunk with 7, 8
ideally I would want something like this:
let sub = firstFeed
.combineLatest(secondFeed)
.compactMap{?}
.sink { firstValue, secondValue in
print("we sunk with \(firstValue), \(secondValue)")
}
I'm not sure what goes into the compact map when two publishers are combined...
CodePudding user response:
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)")
}
CodePudding user response:
You can use filter
and map
to skip nil values:
let sub = firstFeed
.combineLatest(secondFeed)
.filter { $0.0 != nil && $0.1 != nil }
.map { ($0.0!, $0.1!) }
.sink { firstValue, secondValue in
print("we sunk with \(firstValue), \(secondValue)")
}
, or compactMap
to combine the filter
and map
into one, but maybe less readable, condition:
let sub = firstFeed
.combineLatest(secondFeed)
.compactMap { $0.0 != nil && $0.1 != nil ? ($0.0!, $0.1!) : nil }
.sink { firstValue, secondValue in
print("we sunk with \(firstValue), \(secondValue)")
}