Skip to content

Commit be7e1d0

Browse files
authored
🎉 withLatestFrom for more than one publisher. (#22)
1 parent 6e65aba commit be7e1d0

File tree

3 files changed

+332
-8
lines changed

3 files changed

+332
-8
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ This section outlines some of the custom operators CombineExt provides.
8080

8181
### withLatestFrom
8282

83-
Merges two publishers into a single publisher by combining each value from `self` with the _latest_ value from the second publisher, if any.
83+
Merges up to four publishers into a single publisher by combining each value from `self` with the _latest_ value from the other publishers, if any.
8484

8585
```swift
8686
let taps = PassthroughSubject<Void, Never>()

Sources/Operators/WithLatestFrom.swift

+81-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public extension Publisher {
1313
/// Merges two publishers into a single publisher by combining each value
1414
/// from self with the latest value from the second publisher, if any.
1515
///
16-
/// - parameter other: Second observable source.
16+
/// - parameter other: A second publisher source.
1717
/// - parameter resultSelector: Function to invoke for each value from the self combined
1818
/// with the latest value from the second source, if any.
1919
///
@@ -22,19 +22,94 @@ public extension Publisher {
2222
/// specified result selector function.
2323
func withLatestFrom<Other: Publisher, Result>(_ other: Other,
2424
resultSelector: @escaping (Output, Other.Output) -> Result)
25-
-> Publishers.WithLatestFrom<Self, Other, Result> {
26-
return .init(upstream: self, second: other, resultSelector: resultSelector)
25+
-> Publishers.WithLatestFrom<Self, Other, Result> {
26+
return .init(upstream: self, second: other, resultSelector: resultSelector)
27+
}
28+
29+
30+
/// Merges three publishers into a single publisher by combining each value
31+
/// from self with the latest value from the second and third publisher, if any.
32+
///
33+
/// - parameter other: A second publisher source.
34+
/// - parameter other1: A third publisher source.
35+
/// - parameter resultSelector: Function to invoke for each value from the self combined
36+
/// with the latest value from the second and third source, if any.
37+
///
38+
/// - returns: A publisher containing the result of combining each value of the self
39+
/// with the latest value from the second and third publisher, if any, using the
40+
/// specified result selector function.
41+
func withLatestFrom<Other: Publisher, Other1: Publisher, Result>(_ other: Other,
42+
_ other1: Other1,
43+
resultSelector: @escaping (Output, (Other.Output, Other1.Output)) -> Result)
44+
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output), Self.Failure>, Result>
45+
where Other.Failure == Failure, Other1.Failure == Failure {
46+
let combined = other.combineLatest(other1)
47+
.eraseToAnyPublisher()
48+
return .init(upstream: self, second: combined, resultSelector: resultSelector)
49+
}
50+
51+
/// Merges four publishers into a single publisher by combining each value
52+
/// from self with the latest value from the second, third and fourth publisher, if any.
53+
///
54+
/// - parameter other: A second publisher source.
55+
/// - parameter other1: A third publisher source.
56+
/// - parameter other2: A fourth publisher source.
57+
/// - parameter resultSelector: Function to invoke for each value from the self combined
58+
/// with the latest value from the second, third and fourth source, if any.
59+
///
60+
/// - returns: A publisher containing the result of combining each value of the self
61+
/// with the latest value from the second, third and fourth publisher, if any, using the
62+
/// specified result selector function.
63+
func withLatestFrom<Other: Publisher, Other1: Publisher, Other2: Publisher, Result>(_ other: Other,
64+
_ other1: Other1,
65+
_ other2: Other2,
66+
resultSelector: @escaping (Output, (Other.Output, Other1.Output, Other2.Output)) -> Result)
67+
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output, Other2.Output), Self.Failure>, Result>
68+
where Other.Failure == Failure, Other1.Failure == Failure, Other2.Failure == Failure {
69+
let combined = other.combineLatest(other1, other2)
70+
.eraseToAnyPublisher()
71+
return .init(upstream: self, second: combined, resultSelector: resultSelector)
2772
}
2873

2974
/// Upon an emission from self, emit the latest value from the
3075
/// second publisher, if any exists.
3176
///
32-
/// - parameter other: Second observable source.
77+
/// - parameter other: A second publisher source.
3378
///
3479
/// - returns: A publisher containing the latest value from the second publisher, if any.
3580
func withLatestFrom<Other: Publisher>(_ other: Other)
36-
-> Publishers.WithLatestFrom<Self, Other, Other.Output> {
37-
return .init(upstream: self, second: other) { $1 }
81+
-> Publishers.WithLatestFrom<Self, Other, Other.Output> {
82+
return .init(upstream: self, second: other) { $1 }
83+
}
84+
85+
/// Upon an emission from self, emit the latest value from the
86+
/// second and third publisher, if any exists.
87+
///
88+
/// - parameter other: A second publisher source.
89+
/// - parameter other1: A third publisher source.
90+
///
91+
/// - returns: A publisher containing the latest value from the second and third publisher, if any.
92+
func withLatestFrom<Other: Publisher, Other1: Publisher>(_ other: Other,
93+
_ other1: Other1)
94+
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output), Self.Failure>, (Other.Output, Other1.Output)>
95+
where Other.Failure == Failure, Other1.Failure == Failure {
96+
withLatestFrom(other, other1) { $1 }
97+
}
98+
99+
/// Upon an emission from self, emit the latest value from the
100+
/// second, third and forth publisher, if any exists.
101+
///
102+
/// - parameter other: A second publisher source.
103+
/// - parameter other1: A third publisher source.
104+
/// - parameter other2: A forth publisher source.
105+
///
106+
/// - returns: A publisher containing the latest value from the second, third and forth publisher, if any.
107+
func withLatestFrom<Other: Publisher, Other1: Publisher, Other2: Publisher>(_ other: Other,
108+
_ other1: Other1,
109+
_ other2: Other2)
110+
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output, Other2.Output), Self.Failure>, (Other.Output, Other1.Output, Other2.Output)>
111+
where Other.Failure == Failure, Other1.Failure == Failure, Other2.Failure == Failure {
112+
withLatestFrom(other, other1, other2) { $1 }
38113
}
39114
}
40115

Tests/WithLatestFromTests.swift

+250-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class WithLatestFromTests: XCTestCase {
104104
var completed = false
105105

106106
subscription = subject1
107-
.withLatestFrom(subject2)
107+
.withLatestFrom(subject2)
108108
.sink(receiveCompletion: { _ in completed = true },
109109
receiveValue: { results.append($0) })
110110

@@ -135,4 +135,253 @@ class WithLatestFromTests: XCTestCase {
135135
XCTAssertTrue(completed)
136136
subscription.cancel()
137137
}
138+
139+
func testWithLatestFrom2WithResultSelector() {
140+
let subject1 = PassthroughSubject<Int, Never>()
141+
let subject2 = PassthroughSubject<String, Never>()
142+
let subject3 = PassthroughSubject<Bool, Never>()
143+
var results = [String]()
144+
var completed = false
145+
146+
subscription = subject1
147+
.withLatestFrom(subject2, subject3) { "\($0)|\($1.0)|\($1.1)" }
148+
.sink(
149+
receiveCompletion: { _ in completed = true },
150+
receiveValue: { results.append($0) }
151+
)
152+
153+
subject1.send(1)
154+
subject1.send(2)
155+
subject1.send(3)
156+
157+
subject2.send("bar")
158+
159+
subject1.send(4)
160+
subject1.send(5)
161+
162+
subject3.send(true)
163+
164+
subject1.send(10)
165+
166+
subject2.send("foo")
167+
168+
subject1.send(6)
169+
170+
subject2.send("qux")
171+
172+
subject3.send(false)
173+
174+
subject1.send(7)
175+
subject1.send(8)
176+
subject1.send(9)
177+
178+
XCTAssertEqual(results, ["10|bar|true",
179+
"6|foo|true",
180+
"7|qux|false",
181+
"8|qux|false",
182+
"9|qux|false"
183+
])
184+
185+
XCTAssertFalse(completed)
186+
subject2.send(completion: .finished)
187+
XCTAssertFalse(completed)
188+
subject3.send(completion: .finished)
189+
XCTAssertFalse(completed)
190+
subject1.send(completion: .finished)
191+
XCTAssertTrue(completed)
192+
}
193+
194+
func testWithLatestFrom2WithNoResultSelector() {
195+
196+
struct Result: Equatable {
197+
let string: String
198+
let boolean: Bool
199+
}
200+
201+
let subject1 = PassthroughSubject<Int, Never>()
202+
let subject2 = PassthroughSubject<String, Never>()
203+
let subject3 = PassthroughSubject<Bool, Never>()
204+
var results = [Result]()
205+
var completed = false
206+
207+
subscription = subject1
208+
.withLatestFrom(subject2, subject3)
209+
.sink(
210+
receiveCompletion: { _ in completed = true },
211+
receiveValue: { results.append(Result(string: $0.0, boolean: $0.1)) }
212+
)
213+
214+
subject1.send(1)
215+
subject1.send(2)
216+
subject1.send(3)
217+
218+
subject2.send("bar")
219+
220+
subject1.send(4)
221+
subject1.send(5)
222+
223+
subject3.send(true)
224+
225+
subject1.send(10)
226+
227+
subject2.send("foo")
228+
229+
subject1.send(6)
230+
231+
subject2.send("qux")
232+
233+
subject3.send(false)
234+
235+
subject1.send(7)
236+
subject1.send(8)
237+
subject1.send(9)
238+
239+
XCTAssertEqual(results, [Result(string: "bar", boolean: true),
240+
Result(string: "foo", boolean: true),
241+
Result(string: "qux", boolean: false),
242+
Result(string: "qux", boolean: false),
243+
Result(string: "qux", boolean: false)
244+
])
245+
246+
XCTAssertFalse(completed)
247+
subject2.send(completion: .finished)
248+
XCTAssertFalse(completed)
249+
subject3.send(completion: .finished)
250+
XCTAssertFalse(completed)
251+
subject1.send(completion: .finished)
252+
XCTAssertTrue(completed)
253+
}
254+
255+
func testWithLatestFrom3WithResultSelector() {
256+
let subject1 = PassthroughSubject<Int, Never>()
257+
let subject2 = PassthroughSubject<String, Never>()
258+
let subject3 = PassthroughSubject<Bool, Never>()
259+
let subject4 = PassthroughSubject<Int, Never>()
260+
261+
var results = [String]()
262+
var completed = false
263+
264+
subscription = subject1
265+
.withLatestFrom(subject2, subject3, subject4) { "\($0)|\($1.0)|\($1.1)|\($1.2)" }
266+
.sink(
267+
receiveCompletion: { _ in completed = true },
268+
receiveValue: { results.append($0) }
269+
)
270+
271+
subject1.send(1)
272+
subject1.send(2)
273+
subject1.send(3)
274+
275+
subject2.send("bar")
276+
277+
subject1.send(4)
278+
subject1.send(5)
279+
280+
subject3.send(true)
281+
subject4.send(5)
282+
283+
subject1.send(10)
284+
subject4.send(7)
285+
286+
subject2.send("foo")
287+
288+
subject1.send(6)
289+
290+
subject2.send("qux")
291+
292+
subject3.send(false)
293+
294+
subject1.send(7)
295+
subject1.send(8)
296+
subject4.send(8)
297+
subject3.send(true)
298+
subject1.send(9)
299+
300+
XCTAssertEqual(results, ["10|bar|true|5",
301+
"6|foo|true|7",
302+
"7|qux|false|7",
303+
"8|qux|false|7",
304+
"9|qux|true|8"
305+
])
306+
307+
XCTAssertFalse(completed)
308+
subject2.send(completion: .finished)
309+
XCTAssertFalse(completed)
310+
subject3.send(completion: .finished)
311+
XCTAssertFalse(completed)
312+
subject4.send(completion: .finished)
313+
XCTAssertFalse(completed)
314+
subject1.send(completion: .finished)
315+
XCTAssertTrue(completed)
316+
}
317+
318+
func testWithLatestFrom3WithNoResultSelector() {
319+
320+
struct Result: Equatable {
321+
let string: String
322+
let boolean: Bool
323+
let integer: Int
324+
}
325+
326+
let subject1 = PassthroughSubject<Int, Never>()
327+
let subject2 = PassthroughSubject<String, Never>()
328+
let subject3 = PassthroughSubject<Bool, Never>()
329+
let subject4 = PassthroughSubject<Int, Never>()
330+
331+
var results = [Result]()
332+
var completed = false
333+
334+
subscription = subject1
335+
.withLatestFrom(subject2, subject3, subject4)
336+
.sink(
337+
receiveCompletion: { _ in completed = true },
338+
receiveValue: { results.append(Result(string: $0.0, boolean: $0.1, integer: $0.2)) }
339+
)
340+
341+
subject1.send(1)
342+
subject1.send(2)
343+
subject1.send(3)
344+
345+
subject2.send("bar")
346+
347+
subject1.send(4)
348+
subject1.send(5)
349+
350+
subject3.send(true)
351+
subject4.send(5)
352+
353+
subject1.send(10)
354+
subject4.send(7)
355+
356+
subject2.send("foo")
357+
358+
subject1.send(6)
359+
360+
subject2.send("qux")
361+
362+
subject3.send(false)
363+
364+
subject1.send(7)
365+
subject1.send(8)
366+
subject4.send(8)
367+
subject3.send(true)
368+
subject1.send(9)
369+
370+
XCTAssertEqual(results, [Result(string: "bar", boolean: true, integer: 5),
371+
Result(string: "foo", boolean: true, integer: 7),
372+
Result(string: "qux", boolean: false, integer: 7),
373+
Result(string: "qux", boolean: false, integer: 7),
374+
Result(string: "qux", boolean: true, integer: 8)
375+
])
376+
377+
XCTAssertFalse(completed)
378+
subject2.send(completion: .finished)
379+
XCTAssertFalse(completed)
380+
subject3.send(completion: .finished)
381+
XCTAssertFalse(completed)
382+
subject4.send(completion: .finished)
383+
XCTAssertFalse(completed)
384+
subject1.send(completion: .finished)
385+
XCTAssertTrue(completed)
386+
}
138387
}

0 commit comments

Comments
 (0)