forked from CombineCommunity/CombineExt
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
Zip
for multiple Publishers (CombineCommunity#6)
- Loading branch information
Showing
5 changed files
with
229 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// | ||
// ZipMany.swift | ||
// CombineExt | ||
// | ||
// Created by Jasdev Singh on 16/03/2020. | ||
// Copyright © 2020 Combine Community. All rights reserved. | ||
// | ||
|
||
import Combine | ||
|
||
public extension Publisher { | ||
/// Zips `self` with an array of publishers with the same output and failure types. | ||
/// | ||
/// Since there can be any number of `others`, arrays of `Output` values are emitted after zipping. | ||
/// | ||
/// - parameter others: The other publishers to zip with. | ||
/// | ||
/// - returns: A type-erased publisher with value events from each of the inner publishers zipped together in an array. | ||
func zip<Other: Publisher>(with others: [Other]) | ||
-> AnyPublisher<[Output], Failure> where Other.Output == Output, Other.Failure == Failure { | ||
let seed = map { [$0] }.eraseToAnyPublisher() | ||
|
||
return others | ||
.reduce(seed) { zipped, next in | ||
zipped | ||
.zip(next) | ||
.map { $0.0 + [$0.1] } | ||
.eraseToAnyPublisher() | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
/// A variadic overload on `Publisher.zip(with:)`. | ||
func zip<Other: Publisher>(with others: Other...) | ||
-> AnyPublisher<[Output], Failure> where Other.Output == Output, Other.Failure == Failure { | ||
zip(with: others) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// | ||
// ZipManyTests.swift | ||
// CombineExtTests | ||
// | ||
// Created by Jasdev Singh on 16/03/2020. | ||
// Copyright © 2020 Combine Community. All rights reserved. | ||
// | ||
|
||
import XCTest | ||
import Combine | ||
import CombineExt | ||
|
||
final class ZipManyTests: XCTestCase { | ||
private var subscription: AnyCancellable! | ||
|
||
private enum ZipManyError: Error { | ||
case anError | ||
} | ||
|
||
func testOneEmissionZipping() { | ||
let first = PassthroughSubject<Int, Never>() | ||
let second = PassthroughSubject<Int, Never>() | ||
let third = PassthroughSubject<Int, Never>() | ||
|
||
var results = [[Int]]() | ||
var completed = false | ||
|
||
subscription = first | ||
.zip(with: second, third) | ||
.sink(receiveCompletion: { _ in completed = true }, | ||
receiveValue: { results.append($0) }) | ||
|
||
first.send(1) | ||
second.send(2) | ||
third.send(3) | ||
|
||
XCTAssertEqual(results, [[1, 2, 3]]) | ||
XCTAssertFalse(completed) | ||
first.send(completion: .finished) | ||
XCTAssertTrue(completed) | ||
} | ||
|
||
func testMultipleEmissionZippingEndingWithAnError() { | ||
let first = PassthroughSubject<Int, ZipManyError>() | ||
let second = PassthroughSubject<Int, ZipManyError>() | ||
let third = PassthroughSubject<Int, ZipManyError>() | ||
|
||
var results = [[Int]]() | ||
var completed: Subscribers.Completion<ZipManyError>? | ||
|
||
subscription = first | ||
.zip(with: second, third) | ||
.sink(receiveCompletion: { completed = $0 }, | ||
receiveValue: { results.append($0) }) | ||
|
||
first.send(1) | ||
first.send(1) | ||
first.send(1) | ||
first.send(1) | ||
|
||
second.send(2) | ||
second.send(2) | ||
second.send(2) | ||
|
||
third.send(3) | ||
third.send(3) | ||
|
||
XCTAssertEqual(results, [[1, 2, 3], [1, 2, 3]]) | ||
XCTAssertNil(completed) | ||
first.send(completion: .failure(.anError)) | ||
XCTAssertEqual(completed, .failure(.anError)) | ||
} | ||
|
||
func testNoEmissionZipping() { | ||
let first = PassthroughSubject<Int, ZipManyError>() | ||
let second = PassthroughSubject<Int, ZipManyError>() | ||
let third = PassthroughSubject<Int, ZipManyError>() | ||
|
||
var results = [[Int]]() | ||
var completed = false | ||
|
||
subscription = first | ||
.zip(with: second, third) | ||
.sink(receiveCompletion: { _ in completed = true }, | ||
receiveValue: { results.append($0) }) | ||
|
||
first.send(1) | ||
second.send(2) | ||
|
||
// Gated by `third` not emitting. | ||
|
||
XCTAssertTrue(results.isEmpty) | ||
XCTAssertFalse(completed) | ||
} | ||
|
||
func testZippingEndingWithAFinishedCompletion() { | ||
let first = PassthroughSubject<Int, Never>() | ||
let second = PassthroughSubject<Int, Never>() | ||
let third = PassthroughSubject<Int, Never>() | ||
|
||
var results = [[Int]]() | ||
var completed: Subscribers.Completion<Never>? | ||
|
||
subscription = first | ||
.zip(with: second, third) | ||
.sink(receiveCompletion: { completed = $0 }, | ||
receiveValue: { results.append($0) }) | ||
|
||
first.send(1) | ||
|
||
second.send(2) | ||
second.send(2) | ||
|
||
third.send(3) | ||
|
||
XCTAssertEqual(results, [[1, 2, 3]]) | ||
XCTAssertNil(completed) | ||
first.send(completion: .finished) // Triggers a completion, since, there | ||
// aren’t any buffered events from `first` (or `third`) to possibly pair with. | ||
XCTAssertEqual(completed, .finished) | ||
} | ||
|
||
func testZippingWithAnInnerCompletionButNotAnOuter() { | ||
let first = PassthroughSubject<Int, Never>() | ||
let second = PassthroughSubject<Int, Never>() | ||
let third = PassthroughSubject<Int, Never>() | ||
|
||
var results = [[Int]]() | ||
var completed: Subscribers.Completion<Never>? | ||
|
||
subscription = first | ||
.zip(with: second, third) | ||
.sink(receiveCompletion: { completed = $0 }, | ||
receiveValue: { results.append($0) }) | ||
|
||
first.send(1) | ||
first.send(1) | ||
|
||
second.send(2) | ||
second.send(2) | ||
|
||
third.send(3) | ||
|
||
XCTAssertEqual(results, [[1, 2, 3]]) | ||
XCTAssertNil(completed) | ||
first.send(completion: .finished) // Doesn’t trigger a completion, since `first` has an extra un-paired value event. | ||
XCTAssertNil(completed) | ||
} | ||
} |