Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 withLatestFrom for more than one publisher. #22

Merged
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This section outlines some of the custom operators CombineExt provides.

### withLatestFrom

Merges two publishers into a single publisher by combining each value from `self` with the _latest_ value from the second publisher, if any.
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.

```swift
let taps = PassthroughSubject<Void, Never>()
Expand Down
87 changes: 81 additions & 6 deletions Sources/Operators/WithLatestFrom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public extension Publisher {
/// Merges two publishers into a single publisher by combining each value
/// from self with the latest value from the second publisher, if any.
///
/// - parameter other: Second observable source.
/// - parameter other: A second publisher source.
/// - parameter resultSelector: Function to invoke for each value from the self combined
/// with the latest value from the second source, if any.
///
Expand All @@ -22,19 +22,94 @@ public extension Publisher {
/// specified result selector function.
func withLatestFrom<Other: Publisher, Result>(_ other: Other,
resultSelector: @escaping (Output, Other.Output) -> Result)
-> Publishers.WithLatestFrom<Self, Other, Result> {
return .init(upstream: self, second: other, resultSelector: resultSelector)
-> Publishers.WithLatestFrom<Self, Other, Result> {
return .init(upstream: self, second: other, resultSelector: resultSelector)
}


/// Merges three publishers into a single publisher by combining each value
/// from self with the latest value from the second and third publisher, if any.
///
/// - parameter other: A second publisher source.
/// - parameter other1: A third publisher source.
/// - parameter resultSelector: Function to invoke for each value from the self combined
/// with the latest value from the second and third source, if any.
///
/// - returns: A publisher containing the result of combining each value of the self
/// with the latest value from the second and third publisher, if any, using the
/// specified result selector function.
func withLatestFrom<Other: Publisher, Other1: Publisher, Result>(_ other: Other,
_ other1: Other1,
resultSelector: @escaping (Output, (Other.Output, Other1.Output)) -> Result)
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output), Self.Failure>, Result>
where Other.Failure == Failure, Other1.Failure == Failure {
let combined = other.combineLatest(other1)
.eraseToAnyPublisher()
return .init(upstream: self, second: combined, resultSelector: resultSelector)
}

/// Merges four publishers into a single publisher by combining each value
/// from self with the latest value from the second, third and fourth publisher, if any.
///
/// - parameter other: A second publisher source.
/// - parameter other1: A third publisher source.
/// - parameter other2: A fourth publisher source.
/// - parameter resultSelector: Function to invoke for each value from the self combined
/// with the latest value from the second, third and fourth source, if any.
///
/// - returns: A publisher containing the result of combining each value of the self
/// with the latest value from the second, third and fourth publisher, if any, using the
/// specified result selector function.
func withLatestFrom<Other: Publisher, Other1: Publisher, Other2: Publisher, Result>(_ other: Other,
_ other1: Other1,
_ other2: Other2,
resultSelector: @escaping (Output, (Other.Output, Other1.Output, Other2.Output)) -> Result)
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output, Other2.Output), Self.Failure>, Result>
where Other.Failure == Failure, Other1.Failure == Failure, Other2.Failure == Failure {
let combined = other.combineLatest(other1, other2)
.eraseToAnyPublisher()
return .init(upstream: self, second: combined, resultSelector: resultSelector)
}

/// Upon an emission from self, emit the latest value from the
/// second publisher, if any exists.
///
/// - parameter other: Second observable source.
/// - parameter other: A second publisher source.
///
/// - returns: A publisher containing the latest value from the second publisher, if any.
func withLatestFrom<Other: Publisher>(_ other: Other)
-> Publishers.WithLatestFrom<Self, Other, Other.Output> {
return .init(upstream: self, second: other) { $1 }
-> Publishers.WithLatestFrom<Self, Other, Other.Output> {
return .init(upstream: self, second: other) { $1 }
}

/// Upon an emission from self, emit the latest value from the
/// second and third publisher, if any exists.
///
/// - parameter other: A second publisher source.
/// - parameter other1: A third publisher source.
///
/// - returns: A publisher containing the latest value from the second and third publisher, if any.
func withLatestFrom<Other: Publisher, Other1: Publisher>(_ other: Other,
_ other1: Other1)
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output), Self.Failure>, (Other.Output, Other1.Output)>
where Other.Failure == Failure, Other1.Failure == Failure {
withLatestFrom(other, other1) { $1 }
}

/// Upon an emission from self, emit the latest value from the
/// second, third and forth publisher, if any exists.
///
/// - parameter other: A second publisher source.
/// - parameter other1: A third publisher source.
/// - parameter other2: A forth publisher source.
///
/// - returns: A publisher containing the latest value from the second, third and forth publisher, if any.
func withLatestFrom<Other: Publisher, Other1: Publisher, Other2: Publisher>(_ other: Other,
_ other1: Other1,
_ other2: Other2)
-> Publishers.WithLatestFrom<Self, AnyPublisher<(Other.Output, Other1.Output, Other2.Output), Self.Failure>, (Other.Output, Other1.Output, Other2.Output)>
where Other.Failure == Failure, Other1.Failure == Failure, Other2.Failure == Failure {
withLatestFrom(other, other1, other2) { $1 }
}
}

Expand Down
251 changes: 250 additions & 1 deletion Tests/WithLatestFromTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class WithLatestFromTests: XCTestCase {
var completed = false

subscription = subject1
.withLatestFrom(subject2)
.withLatestFrom(subject2)
.sink(receiveCompletion: { _ in completed = true },
receiveValue: { results.append($0) })

Expand Down Expand Up @@ -135,4 +135,253 @@ class WithLatestFromTests: XCTestCase {
XCTAssertTrue(completed)
subscription.cancel()
}

func testWithLatestFrom2WithResultSelector() {
let subject1 = PassthroughSubject<Int, Never>()
let subject2 = PassthroughSubject<String, Never>()
let subject3 = PassthroughSubject<Bool, Never>()
var results = [String]()
var completed = false

subscription = subject1
.withLatestFrom(subject2, subject3) { "\($0)|\($1.0)|\($1.1)" }
.sink(
receiveCompletion: { _ in completed = true },
receiveValue: { results.append($0) }
)

subject1.send(1)
subject1.send(2)
subject1.send(3)

subject2.send("bar")

subject1.send(4)
subject1.send(5)

subject3.send(true)

subject1.send(10)

subject2.send("foo")

subject1.send(6)

subject2.send("qux")

subject3.send(false)

subject1.send(7)
subject1.send(8)
subject1.send(9)

XCTAssertEqual(results, ["10|bar|true",
"6|foo|true",
"7|qux|false",
"8|qux|false",
"9|qux|false"
])

XCTAssertFalse(completed)
subject2.send(completion: .finished)
XCTAssertFalse(completed)
subject3.send(completion: .finished)
XCTAssertFalse(completed)
subject1.send(completion: .finished)
XCTAssertTrue(completed)
}

func testWithLatestFrom2WithNoResultSelector() {

struct Result: Equatable {
let string: String
let boolean: Bool
}

let subject1 = PassthroughSubject<Int, Never>()
let subject2 = PassthroughSubject<String, Never>()
let subject3 = PassthroughSubject<Bool, Never>()
var results = [Result]()
var completed = false

subscription = subject1
.withLatestFrom(subject2, subject3)
.sink(
receiveCompletion: { _ in completed = true },
receiveValue: { results.append(Result(string: $0.0, boolean: $0.1)) }
)

subject1.send(1)
subject1.send(2)
subject1.send(3)

subject2.send("bar")

subject1.send(4)
subject1.send(5)

subject3.send(true)

subject1.send(10)

subject2.send("foo")

subject1.send(6)

subject2.send("qux")

subject3.send(false)

subject1.send(7)
subject1.send(8)
subject1.send(9)

XCTAssertEqual(results, [Result(string: "bar", boolean: true),
Result(string: "foo", boolean: true),
Result(string: "qux", boolean: false),
Result(string: "qux", boolean: false),
Result(string: "qux", boolean: false)
])

XCTAssertFalse(completed)
subject2.send(completion: .finished)
XCTAssertFalse(completed)
subject3.send(completion: .finished)
XCTAssertFalse(completed)
subject1.send(completion: .finished)
XCTAssertTrue(completed)
}

func testWithLatestFrom3WithResultSelector() {
let subject1 = PassthroughSubject<Int, Never>()
let subject2 = PassthroughSubject<String, Never>()
let subject3 = PassthroughSubject<Bool, Never>()
let subject4 = PassthroughSubject<Int, Never>()

var results = [String]()
var completed = false

subscription = subject1
.withLatestFrom(subject2, subject3, subject4) { "\($0)|\($1.0)|\($1.1)|\($1.2)" }
.sink(
receiveCompletion: { _ in completed = true },
receiveValue: { results.append($0) }
)

subject1.send(1)
subject1.send(2)
subject1.send(3)

subject2.send("bar")

subject1.send(4)
subject1.send(5)

subject3.send(true)
subject4.send(5)

subject1.send(10)
subject4.send(7)

subject2.send("foo")

subject1.send(6)

subject2.send("qux")

subject3.send(false)

subject1.send(7)
subject1.send(8)
subject4.send(8)
subject3.send(true)
subject1.send(9)

XCTAssertEqual(results, ["10|bar|true|5",
"6|foo|true|7",
"7|qux|false|7",
"8|qux|false|7",
"9|qux|true|8"
])

XCTAssertFalse(completed)
subject2.send(completion: .finished)
XCTAssertFalse(completed)
subject3.send(completion: .finished)
XCTAssertFalse(completed)
subject4.send(completion: .finished)
XCTAssertFalse(completed)
subject1.send(completion: .finished)
XCTAssertTrue(completed)
}

func testWithLatestFrom3WithNoResultSelector() {

struct Result: Equatable {
let string: String
let boolean: Bool
let integer: Int
}

let subject1 = PassthroughSubject<Int, Never>()
let subject2 = PassthroughSubject<String, Never>()
let subject3 = PassthroughSubject<Bool, Never>()
let subject4 = PassthroughSubject<Int, Never>()

var results = [Result]()
var completed = false

subscription = subject1
.withLatestFrom(subject2, subject3, subject4)
.sink(
receiveCompletion: { _ in completed = true },
receiveValue: { results.append(Result(string: $0.0, boolean: $0.1, integer: $0.2)) }
)

subject1.send(1)
subject1.send(2)
subject1.send(3)

subject2.send("bar")

subject1.send(4)
subject1.send(5)

subject3.send(true)
subject4.send(5)

subject1.send(10)
subject4.send(7)

subject2.send("foo")

subject1.send(6)

subject2.send("qux")

subject3.send(false)

subject1.send(7)
subject1.send(8)
subject4.send(8)
subject3.send(true)
subject1.send(9)

XCTAssertEqual(results, [Result(string: "bar", boolean: true, integer: 5),
Result(string: "foo", boolean: true, integer: 7),
Result(string: "qux", boolean: false, integer: 7),
Result(string: "qux", boolean: false, integer: 7),
Result(string: "qux", boolean: true, integer: 8)
])

XCTAssertFalse(completed)
subject2.send(completion: .finished)
XCTAssertFalse(completed)
subject3.send(completion: .finished)
XCTAssertFalse(completed)
subject4.send(completion: .finished)
XCTAssertFalse(completed)
subject1.send(completion: .finished)
XCTAssertTrue(completed)
}
}