Compare commits
15 Commits
0.7.0
...
merge-publisher
| Author | SHA1 | Date | |
|---|---|---|---|
| af0ae86407 | |||
| f861335dc3 | |||
| 769c3c818f | |||
| 910d21da4c | |||
| 6e20956d6d | |||
| e453879d75 | |||
| 98f6b6b337 | |||
| 74b739d74e | |||
| bcba9a19d4 | |||
| 486e166462 | |||
| c6536cf8d3 | |||
| cf41c25cf7 | |||
| b4557fb311 | |||
| f8e6e66ab4 | |||
| 95b42abce3 |
+22
-16
@@ -1,16 +1,17 @@
|
||||
version: 2
|
||||
jobs:
|
||||
"Execute tests on macOS 10.15.0 (Xcode 11.2.0, Swift 5.1.2)":
|
||||
"Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
macos:
|
||||
xcode: "11.2.0"
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1.2"
|
||||
SWIFT_VERSION: "5.1.3"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: |
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-code-coverage --build-path .build-test-debug"
|
||||
xcrun llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
@@ -20,15 +21,17 @@ jobs:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: |
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-release"
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-xcodeproj
|
||||
command: make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
- run:
|
||||
name: Building for testing on macOS 10.15.0 with xcodebuild
|
||||
command: |
|
||||
@@ -60,35 +63,35 @@ jobs:
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
"Execute compatibility tests on iOS 13.2.2 (Xcode 11.2.0, Swift 5.1.2)":
|
||||
"Execute compatibility tests on iOS 13.3 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
macos:
|
||||
xcode: "11.2.0"
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1.2"
|
||||
SWIFT_VERSION: "5.1.3"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-compatibility-xcodeproj
|
||||
- run:
|
||||
name: Building for testing on iOS 13.2.2 with xcodebuild
|
||||
name: Building for testing on iOS 13.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.2.2" \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing against Combine on iOS 13.2.2 with xcodebuild
|
||||
name: Testing against Combine on iOS 13.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.2.2" \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
@@ -125,7 +128,7 @@ jobs:
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: |
|
||||
make generate-xcodeproj
|
||||
make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
xcodebuild -scheme OpenCombine-Package -showdestinations
|
||||
- run:
|
||||
name: Building for testing on iOS 9.3 with xcodebuild
|
||||
@@ -182,6 +185,7 @@ jobs:
|
||||
> /dev/null 2>&1 \
|
||||
|| true
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--enable-code-coverage \
|
||||
@@ -194,6 +198,7 @@ jobs:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: | # We need to run the test command twice because of https://bugs.swift.org/browse/SR-10783
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--build-path .build-test-debug-sanitize-thread" \
|
||||
@@ -207,6 +212,7 @@ jobs:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--build-path .build-test-release"
|
||||
@@ -217,7 +223,7 @@ jobs:
|
||||
|
||||
"Run SwiftLint and Danger":
|
||||
macos:
|
||||
xcode: "11.2.0"
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
@@ -236,7 +242,7 @@ jobs:
|
||||
|
||||
"Run Pod spec lint":
|
||||
macos:
|
||||
xcode: "11.2.0"
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
@@ -250,10 +256,10 @@ workflows:
|
||||
version: 2
|
||||
"OpenCombine: execute tests on macOS":
|
||||
jobs:
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 11.2.0, Swift 5.1.2)"
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)"
|
||||
"OpenCombine: execute compatibility tests":
|
||||
jobs:
|
||||
- "Execute compatibility tests on iOS 13.2.2 (Xcode 11.2.0, Swift 5.1.2)"
|
||||
- "Execute compatibility tests on iOS 13.3 (Xcode 11.3.0, Swift 5.1.3)"
|
||||
"OpenCombine: execute tests on iOS":
|
||||
jobs:
|
||||
- "Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ GEM
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
emoji_regex (1.0.1)
|
||||
excon (0.68.0)
|
||||
excon (0.71.0)
|
||||
faraday (0.17.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineFoundation"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "OpenCombine + OpenCombineFoundation interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
Adds publishers to Foundation types like NotificationCenter, URLSession etc.
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
spec.osx.deployment_target = "10.10"
|
||||
spec.ios.deployment_target = "8.0"
|
||||
spec.watchos.deployment_target = "2.0"
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineFoundation/**/*.swift"
|
||||
spec.dependency "OpenCombine", '~> 0.6'
|
||||
end
|
||||
+5
-1
@@ -7,14 +7,18 @@ let package = Package(
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch"],
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
|
||||
@@ -204,47 +204,10 @@ extension Publishers {
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == [Upstream.Output]
|
||||
}
|
||||
|
||||
/// A publisher that buffers a maximum number of items.
|
||||
public struct CollectByCount<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = [Upstream.Output]
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The maximum number of received elements to buffer before publishing.
|
||||
public let count: Int
|
||||
|
||||
public init(upstream: Upstream, count: Int)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == [Upstream.Output]
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Collects up to the specified number of elements, and then emits a single array of the collection.
|
||||
///
|
||||
/// If the upstream publisher finishes before filling the buffer, this publisher sends an array of all the items it has received. This may be fewer than `count` elements.
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error to the downstream receiver instead of sending its output.
|
||||
/// Note: When this publisher receives a request for `.max(n)` elements, it requests `.max(count * n)` from the upstream publisher.
|
||||
/// - Parameter count: The maximum number of received elements to buffer before publishing.
|
||||
/// - Returns: A publisher that collects up to the specified number of elements, and then publishes them as an array.
|
||||
public func collect(_ count: Int) -> Publishers.CollectByCount<Self>
|
||||
|
||||
/// Collects elements by a given strategy, and emits a single array of the collection.
|
||||
///
|
||||
/// If the upstream publisher finishes before filling the buffer, this publisher sends an array of all the items it has received. This may be fewer than `count` elements.
|
||||
@@ -298,396 +261,6 @@ extension Publisher {
|
||||
public func prefix<P>(untilOutputFrom publisher: P) -> Publishers.PrefixUntilOutput<Self, P> where P : Publisher
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the merge function to two upstream publishers.
|
||||
public struct Merge<A, B> : Publisher where A : Publisher, B : Publisher, A.Failure == B.Failure, A.Output == B.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public init(_ a: A, _ b: B)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, B.Failure == S.Failure, B.Output == S.Input
|
||||
|
||||
public func merge<P>(with other: P) -> Publishers.Merge3<A, B, P> where P : Publisher, B.Failure == P.Failure, B.Output == P.Output
|
||||
|
||||
public func merge<Z, Y>(with z: Z, _ y: Y) -> Publishers.Merge4<A, B, Z, Y> where Z : Publisher, Y : Publisher, B.Failure == Z.Failure, B.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output
|
||||
|
||||
public func merge<Z, Y, X>(with z: Z, _ y: Y, _ x: X) -> Publishers.Merge5<A, B, Z, Y, X> where Z : Publisher, Y : Publisher, X : Publisher, B.Failure == Z.Failure, B.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output
|
||||
|
||||
public func merge<Z, Y, X, W>(with z: Z, _ y: Y, _ x: X, _ w: W) -> Publishers.Merge6<A, B, Z, Y, X, W> where Z : Publisher, Y : Publisher, X : Publisher, W : Publisher, B.Failure == Z.Failure, B.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output, X.Failure == W.Failure, X.Output == W.Output
|
||||
|
||||
public func merge<Z, Y, X, W, V>(with z: Z, _ y: Y, _ x: X, _ w: W, _ v: V) -> Publishers.Merge7<A, B, Z, Y, X, W, V> where Z : Publisher, Y : Publisher, X : Publisher, W : Publisher, V : Publisher, B.Failure == Z.Failure, B.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output, X.Failure == W.Failure, X.Output == W.Output, W.Failure == V.Failure, W.Output == V.Output
|
||||
|
||||
public func merge<Z, Y, X, W, V, U>(with z: Z, _ y: Y, _ x: X, _ w: W, _ v: V, _ u: U) -> Publishers.Merge8<A, B, Z, Y, X, W, V, U> where Z : Publisher, Y : Publisher, X : Publisher, W : Publisher, V : Publisher, U : Publisher, B.Failure == Z.Failure, B.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output, X.Failure == W.Failure, X.Output == W.Output, W.Failure == V.Failure, W.Output == V.Output, V.Failure == U.Failure, V.Output == U.Output
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to three upstream publishers.
|
||||
public struct Merge3<A, B, C> : Publisher where A : Publisher, B : Publisher, C : Publisher, A.Failure == B.Failure, A.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, C.Failure == S.Failure, C.Output == S.Input
|
||||
|
||||
public func merge<P>(with other: P) -> Publishers.Merge4<A, B, C, P> where P : Publisher, C.Failure == P.Failure, C.Output == P.Output
|
||||
|
||||
public func merge<Z, Y>(with z: Z, _ y: Y) -> Publishers.Merge5<A, B, C, Z, Y> where Z : Publisher, Y : Publisher, C.Failure == Z.Failure, C.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output
|
||||
|
||||
public func merge<Z, Y, X>(with z: Z, _ y: Y, _ x: X) -> Publishers.Merge6<A, B, C, Z, Y, X> where Z : Publisher, Y : Publisher, X : Publisher, C.Failure == Z.Failure, C.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output
|
||||
|
||||
public func merge<Z, Y, X, W>(with z: Z, _ y: Y, _ x: X, _ w: W) -> Publishers.Merge7<A, B, C, Z, Y, X, W> where Z : Publisher, Y : Publisher, X : Publisher, W : Publisher, C.Failure == Z.Failure, C.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output, X.Failure == W.Failure, X.Output == W.Output
|
||||
|
||||
public func merge<Z, Y, X, W, V>(with z: Z, _ y: Y, _ x: X, _ w: W, _ v: V) -> Publishers.Merge8<A, B, C, Z, Y, X, W, V> where Z : Publisher, Y : Publisher, X : Publisher, W : Publisher, V : Publisher, C.Failure == Z.Failure, C.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output, X.Failure == W.Failure, X.Output == W.Output, W.Failure == V.Failure, W.Output == V.Output
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to four upstream publishers.
|
||||
public struct Merge4<A, B, C, D> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, A.Failure == B.Failure, A.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C, _ d: D)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, D.Failure == S.Failure, D.Output == S.Input
|
||||
|
||||
public func merge<P>(with other: P) -> Publishers.Merge5<A, B, C, D, P> where P : Publisher, D.Failure == P.Failure, D.Output == P.Output
|
||||
|
||||
public func merge<Z, Y>(with z: Z, _ y: Y) -> Publishers.Merge6<A, B, C, D, Z, Y> where Z : Publisher, Y : Publisher, D.Failure == Z.Failure, D.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output
|
||||
|
||||
public func merge<Z, Y, X>(with z: Z, _ y: Y, _ x: X) -> Publishers.Merge7<A, B, C, D, Z, Y, X> where Z : Publisher, Y : Publisher, X : Publisher, D.Failure == Z.Failure, D.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output
|
||||
|
||||
public func merge<Z, Y, X, W>(with z: Z, _ y: Y, _ x: X, _ w: W) -> Publishers.Merge8<A, B, C, D, Z, Y, X, W> where Z : Publisher, Y : Publisher, X : Publisher, W : Publisher, D.Failure == Z.Failure, D.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output, X.Failure == W.Failure, X.Output == W.Output
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to five upstream publishers.
|
||||
public struct Merge5<A, B, C, D, E> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, E : Publisher, A.Failure == B.Failure, A.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, E.Failure == S.Failure, E.Output == S.Input
|
||||
|
||||
public func merge<P>(with other: P) -> Publishers.Merge6<A, B, C, D, E, P> where P : Publisher, E.Failure == P.Failure, E.Output == P.Output
|
||||
|
||||
public func merge<Z, Y>(with z: Z, _ y: Y) -> Publishers.Merge7<A, B, C, D, E, Z, Y> where Z : Publisher, Y : Publisher, E.Failure == Z.Failure, E.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output
|
||||
|
||||
public func merge<Z, Y, X>(with z: Z, _ y: Y, _ x: X) -> Publishers.Merge8<A, B, C, D, E, Z, Y, X> where Z : Publisher, Y : Publisher, X : Publisher, E.Failure == Z.Failure, E.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output, Y.Failure == X.Failure, Y.Output == X.Output
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to six upstream publishers.
|
||||
public struct Merge6<A, B, C, D, E, F> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, E : Publisher, F : Publisher, A.Failure == B.Failure, A.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output, E.Failure == F.Failure, E.Output == F.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, F.Failure == S.Failure, F.Output == S.Input
|
||||
|
||||
public func merge<P>(with other: P) -> Publishers.Merge7<A, B, C, D, E, F, P> where P : Publisher, F.Failure == P.Failure, F.Output == P.Output
|
||||
|
||||
public func merge<Z, Y>(with z: Z, _ y: Y) -> Publishers.Merge8<A, B, C, D, E, F, Z, Y> where Z : Publisher, Y : Publisher, F.Failure == Z.Failure, F.Output == Z.Output, Z.Failure == Y.Failure, Z.Output == Y.Output
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to seven upstream publishers.
|
||||
public struct Merge7<A, B, C, D, E, F, G> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, E : Publisher, F : Publisher, G : Publisher, A.Failure == B.Failure, A.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output, E.Failure == F.Failure, E.Output == F.Output, F.Failure == G.Failure, F.Output == G.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public let g: G
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, G.Failure == S.Failure, G.Output == S.Input
|
||||
|
||||
public func merge<P>(with other: P) -> Publishers.Merge8<A, B, C, D, E, F, G, P> where P : Publisher, G.Failure == P.Failure, G.Output == P.Output
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to eight upstream publishers.
|
||||
public struct Merge8<A, B, C, D, E, F, G, H> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, E : Publisher, F : Publisher, G : Publisher, H : Publisher, A.Failure == B.Failure, A.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output, E.Failure == F.Failure, E.Output == F.Output, F.Failure == G.Failure, F.Output == G.Output, G.Failure == H.Failure, G.Output == H.Output {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public let g: G
|
||||
|
||||
public let h: H
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, H.Failure == S.Failure, H.Output == S.Input
|
||||
}
|
||||
|
||||
public struct MergeMany<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let publishers: [Upstream]
|
||||
|
||||
public init(_ upstream: Upstream...)
|
||||
|
||||
public init<S>(_ upstream: S) where Upstream == S.Element, S : Sequence
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
|
||||
public func merge(with other: Upstream) -> Publishers.MergeMany<Upstream>
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
/// - Parameter other: Another publisher.
|
||||
/// - Returns: A publisher that emits an event when either upstream publisher emits an event.
|
||||
public func merge<P>(with other: P) -> Publishers.Merge<Self, P> where P : Publisher, Self.Failure == P.Failure, Self.Output == P.Output
|
||||
|
||||
/// Combines elements from this publisher with those from two other publishers, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<B, C>(with b: B, _ c: C) -> Publishers.Merge3<Self, B, C> where B : Publisher, C : Publisher, Self.Failure == B.Failure, Self.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output
|
||||
|
||||
/// Combines elements from this publisher with those from three other publishers, delivering
|
||||
/// an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits an event.
|
||||
public func merge<B, C, D>(with b: B, _ c: C, _ d: D) -> Publishers.Merge4<Self, B, C, D> where B : Publisher, C : Publisher, D : Publisher, Self.Failure == B.Failure, Self.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output
|
||||
|
||||
/// Combines elements from this publisher with those from four other publishers, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits an event.
|
||||
public func merge<B, C, D, E>(with b: B, _ c: C, _ d: D, _ e: E) -> Publishers.Merge5<Self, B, C, D, E> where B : Publisher, C : Publisher, D : Publisher, E : Publisher, Self.Failure == B.Failure, Self.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output
|
||||
|
||||
/// Combines elements from this publisher with those from five other publishers, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits an event.
|
||||
public func merge<B, C, D, E, F>(with b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Publishers.Merge6<Self, B, C, D, E, F> where B : Publisher, C : Publisher, D : Publisher, E : Publisher, F : Publisher, Self.Failure == B.Failure, Self.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output, E.Failure == F.Failure, E.Output == F.Output
|
||||
|
||||
/// Combines elements from this publisher with those from six other publishers, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - g: A seventh publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits an event.
|
||||
public func merge<B, C, D, E, F, G>(with b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Publishers.Merge7<Self, B, C, D, E, F, G> where B : Publisher, C : Publisher, D : Publisher, E : Publisher, F : Publisher, G : Publisher, Self.Failure == B.Failure, Self.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output, E.Failure == F.Failure, E.Output == F.Output, F.Failure == G.Failure, F.Output == G.Output
|
||||
|
||||
/// Combines elements from this publisher with those from seven other publishers, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers finish. If an upstream publisher produces an error, the merged publisher fails with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - g: A seventh publisher.
|
||||
/// - h: An eighth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits an event.
|
||||
public func merge<B, C, D, E, F, G, H>(with b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Publishers.Merge8<Self, B, C, D, E, F, G, H> where B : Publisher, C : Publisher, D : Publisher, E : Publisher, F : Publisher, G : Publisher, H : Publisher, Self.Failure == B.Failure, Self.Output == B.Output, B.Failure == C.Failure, B.Output == C.Output, C.Failure == D.Failure, C.Output == D.Output, D.Failure == E.Failure, D.Output == E.Output, E.Failure == F.Failure, E.Output == F.Output, F.Failure == G.Failure, F.Output == G.Output, G.Failure == H.Failure, G.Output == H.Output
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher of the same type, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// - Parameter other: Another publisher of this publisher's type.
|
||||
/// - Returns: A publisher that emits an event when either upstream publisher emits
|
||||
/// an event.
|
||||
public func merge(with other: Self) -> Publishers.MergeMany<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that “flattens” nested publishers.
|
||||
@@ -840,148 +413,6 @@ extension Publisher {
|
||||
public func throttle<S>(for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: Bool) -> Publishers.Throttle<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that replaces an empty stream with a provided element.
|
||||
public struct ReplaceEmpty<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The element to deliver when the upstream publisher finishes without delivering any elements.
|
||||
public let output: Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream, output: Publishers.ReplaceEmpty<Upstream>.Output)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces an empty stream with the provided element.
|
||||
///
|
||||
/// If the upstream publisher finishes without producing any elements, this publisher emits the provided element, then finishes normally.
|
||||
/// - Parameter output: An element to emit when the upstream publisher finishes without emitting any elements.
|
||||
/// - Returns: A publisher that replaces an empty stream with the provided output element.
|
||||
public func replaceEmpty(with output: Self.Output) -> Publishers.ReplaceEmpty<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that raises a fatal error upon receiving any failure, and otherwise republishes all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but do not impact performance of shipping code.
|
||||
public struct AssertNoFailure<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The string used at the beginning of the fatal error message.
|
||||
public let prefix: String
|
||||
|
||||
/// The filename used in the error message.
|
||||
public let file: StaticString
|
||||
|
||||
/// The line number used in the error message.
|
||||
public let line: UInt
|
||||
|
||||
public init(upstream: Upstream, prefix: String, file: StaticString, line: UInt)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Output == S.Input, S.Failure == Publishers.AssertNoFailure<Upstream>.Failure
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but do not impact performance of shipping code.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - prefix: A string used at the beginning of the fatal error message.
|
||||
/// - file: A filename used in the error message. This defaults to `#file`.
|
||||
/// - line: A line number used in the error message. This defaults to `#line`.
|
||||
/// - Returns: A publisher that raises a fatal error when its upstream publisher fails.
|
||||
public func assertNoFailure(_ prefix: String = "", file: StaticString = #file, line: UInt = #line) -> Publishers.AssertNoFailure<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that ignores elements from the upstream publisher until it receives an element from second publisher.
|
||||
public struct DropUntilOutput<Upstream, Other> : Publisher where Upstream : Publisher, Other : Publisher, Upstream.Failure == Other.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A publisher to monitor for its first emitted element.
|
||||
public let other: Other
|
||||
|
||||
/// Creates a publisher that ignores elements from the upstream publisher until it receives an element from another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: A publisher to drop elements from while waiting for another publisher to emit elements.
|
||||
/// - other: A publisher to monitor for its first emitted element.
|
||||
public init(upstream: Upstream, other: Other)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Output == S.Input, Other.Failure == S.Failure
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ignores elements from the upstream publisher until it receives an element from a second publisher.
|
||||
///
|
||||
/// This publisher requests a single value from the upstream publisher, and it ignores (drops) all elements from that publisher until the upstream publisher produces a value. After the `other` publisher produces an element, this publisher cancels its subscription to the `other` publisher, and allows events from the `upstream` publisher to pass through.
|
||||
/// After this publisher receives a subscription from the upstream publisher, it passes through backpressure requests from downstream to the upstream publisher. If the upstream publisher acts on those requests before the other publisher produces an item, this publisher drops the elements it receives from the upstream publisher.
|
||||
///
|
||||
/// - Parameter publisher: A publisher to monitor for its first emitted element.
|
||||
/// - Returns: A publisher that drops elements from the upstream publisher until the `other` publisher produces a value.
|
||||
public func drop<P>(untilOutputFrom publisher: P) -> Publishers.DropUntilOutput<Self, P> where P : Publisher, Self.Failure == P.Failure
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes elements only after a specified time interval elapses between events.
|
||||
@@ -1489,109 +920,6 @@ extension Publishers.CombineLatest4 : Equatable where A : Equatable, B : Equatab
|
||||
public static func == (lhs: Publishers.CombineLatest4<A, B, C, D>, rhs: Publishers.CombineLatest4<A, B, C, D>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.CollectByCount : Equatable where Upstream : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func == (lhs: Publishers.CollectByCount<Upstream>, rhs: Publishers.CollectByCount<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality..
|
||||
/// - Returns: `true` if the two merging - rhs: Another merging publisher to compare for equality.
|
||||
public static func == (lhs: Publishers.Merge<A, B>, rhs: Publishers.Merge<A, B>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge3 : Equatable where A : Equatable, B : Equatable, C : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality.
|
||||
/// - Returns: `true` if the two merging publishers have equal source publishers, `false` otherwise.
|
||||
public static func == (lhs: Publishers.Merge3<A, B, C>, rhs: Publishers.Merge3<A, B, C>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge4 : Equatable where A : Equatable, B : Equatable, C : Equatable, D : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality.
|
||||
/// - Returns: `true` if the two merging publishers have equal source publishers, `false` otherwise.
|
||||
public static func == (lhs: Publishers.Merge4<A, B, C, D>, rhs: Publishers.Merge4<A, B, C, D>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge5 : Equatable where A : Equatable, B : Equatable, C : Equatable, D : Equatable, E : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality.
|
||||
/// - Returns: `true` if the two merging publishers have equal source publishers, `false` otherwise.
|
||||
public static func == (lhs: Publishers.Merge5<A, B, C, D, E>, rhs: Publishers.Merge5<A, B, C, D, E>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge6 : Equatable where A : Equatable, B : Equatable, C : Equatable, D : Equatable, E : Equatable, F : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality.
|
||||
/// - Returns: `true` if the two merging publishers have equal source publishers, `false` otherwise.
|
||||
public static func == (lhs: Publishers.Merge6<A, B, C, D, E, F>, rhs: Publishers.Merge6<A, B, C, D, E, F>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge7 : Equatable where A : Equatable, B : Equatable, C : Equatable, D : Equatable, E : Equatable, F : Equatable, G : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality.
|
||||
/// - Returns: `true` if the two merging publishers have equal source publishers, `false` otherwise.
|
||||
public static func == (lhs: Publishers.Merge7<A, B, C, D, E, F, G>, rhs: Publishers.Merge7<A, B, C, D, E, F, G>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Merge8 : Equatable where A : Equatable, B : Equatable, C : Equatable, D : Equatable, E : Equatable, F : Equatable, G : Equatable, H : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A merging publisher to compare for equality.
|
||||
/// - rhs: Another merging publisher to compare for equality.
|
||||
/// - Returns: `true` if the two merging publishers have equal source publishers, `false` otherwise.
|
||||
public static func == (lhs: Publishers.Merge8<A, B, C, D, E, F, G, H>, rhs: Publishers.Merge8<A, B, C, D, E, F, G, H>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.MergeMany : Equatable where Upstream : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func == (lhs: Publishers.MergeMany<Upstream>, rhs: Publishers.MergeMany<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Retry : Equatable where Upstream : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
@@ -1605,30 +933,6 @@ extension Publishers.Retry : Equatable where Upstream : Equatable {
|
||||
public static func == (lhs: Publishers.Retry<Upstream>, rhs: Publishers.Retry<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.ReplaceEmpty : Equatable where Upstream : Equatable, Upstream.Output : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A replace empty publisher to compare for equality.
|
||||
/// - rhs: Another replace empty publisher to compare for equality.
|
||||
/// - Returns: `true` if the two publishers have equal upstream publishers and output elements, `false` otherwise.
|
||||
public static func == (lhs: Publishers.ReplaceEmpty<Upstream>, rhs: Publishers.ReplaceEmpty<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.DropUntilOutput : Equatable where Upstream : Equatable, Other : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func == (lhs: Publishers.DropUntilOutput<Upstream, Other>, rhs: Publishers.DropUntilOutput<Upstream, Other>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Zip : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// From /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/swift/Foundation.swiftmodule/x86_64.swiftinterface
|
||||
// swift-interface-format-version: 1.0
|
||||
// swift-compiler-version: Apple Swift version 5.1.1 (swiftlang-1100.8.275.1 clang-1100.0.32.1)
|
||||
// swift-module-flags: -target x86_64-apple-macosx10.15 -enable-objc-interop -autolink-force-load -enable-library-evolution -module-link-name swiftFoundation -swift-version 5 -O -enforce-exclusivity=unchecked -module-name Foundation
|
||||
|
||||
public typealias Published = Combine.Published
|
||||
|
||||
public typealias ObservableObject = Combine.ObservableObject
|
||||
|
||||
public protocol _KeyValueCodingAndObservingPublishing {
|
||||
}
|
||||
|
||||
extension NSObject : Foundation._KeyValueCodingAndObservingPublishing {
|
||||
}
|
||||
|
||||
extension _KeyValueCodingAndObservingPublishing where Self : ObjectiveC.NSObject {
|
||||
public func publisher<Value>(for keyPath: Swift.KeyPath<Self, Value>, options: Foundation.NSKeyValueObservingOptions = [.initial, .new]) -> ObjectiveC.NSObject.KeyValueObservingPublisher<Self, Value>
|
||||
}
|
||||
|
||||
extension NSObject.KeyValueObservingPublisher {
|
||||
public func didChange() -> Combine.Publishers.Map<ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, Swift.Void>
|
||||
}
|
||||
|
||||
extension NSObject {
|
||||
public struct KeyValueObservingPublisher<Subject, Value> : Swift.Equatable where Subject : ObjectiveC.NSObject {
|
||||
public let object: Subject
|
||||
public let keyPath: Swift.KeyPath<Subject, Value>
|
||||
public let options: Foundation.NSKeyValueObservingOptions
|
||||
public init(object: Subject, keyPath: Swift.KeyPath<Subject, Value>, options: Foundation.NSKeyValueObservingOptions)
|
||||
public static func == (lhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, rhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>) -> Swift.Bool
|
||||
}
|
||||
}
|
||||
|
||||
extension NSObject.KeyValueObservingPublisher : Combine.Publisher {
|
||||
public typealias Output = Value
|
||||
public typealias Failure = Swift.Never
|
||||
public func receive<S>(subscriber: S) where Value == S.Input, S : Combine.Subscriber, S.Failure == ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>.Failure
|
||||
}
|
||||
|
||||
extension Timer {
|
||||
public static func publish(every interval: Foundation.TimeInterval, tolerance: Foundation.TimeInterval? = nil, on runLoop: Foundation.RunLoop, in mode: Foundation.RunLoop.Mode, options: Foundation.RunLoop.SchedulerOptions? = nil) -> Foundation.Timer.TimerPublisher
|
||||
final public class TimerPublisher : Combine.ConnectablePublisher {
|
||||
public typealias Output = Foundation.Date
|
||||
public typealias Failure = Swift.Never
|
||||
final public let interval: Foundation.TimeInterval
|
||||
final public let tolerance: Foundation.TimeInterval?
|
||||
final public let runLoop: Foundation.RunLoop
|
||||
final public let mode: Foundation.RunLoop.Mode
|
||||
final public let options: Foundation.RunLoop.SchedulerOptions?
|
||||
public init(interval: Foundation.TimeInterval, tolerance: Foundation.TimeInterval? = nil, runLoop: Foundation.RunLoop, mode: Foundation.RunLoop.Mode, options: Foundation.RunLoop.SchedulerOptions? = nil)
|
||||
final public func receive<S>(subscriber: S) where S : Combine.Subscriber, S.Failure == Foundation.Timer.TimerPublisher.Failure, S.Input == Foundation.Timer.TimerPublisher.Output
|
||||
final public func connect() -> Combine.Cancellable
|
||||
@objc deinit
|
||||
}
|
||||
}
|
||||
|
||||
extension OperationQueue : Combine.Scheduler {
|
||||
public struct SchedulerTimeType : Swift.Strideable, Swift.Codable, Swift.Hashable {
|
||||
public var date: Foundation.Date
|
||||
public init(_ date: Foundation.Date)
|
||||
public func distance(to other: Foundation.OperationQueue.SchedulerTimeType) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public func advanced(by n: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType
|
||||
public struct Stride : Swift.ExpressibleByFloatLiteral, Swift.Comparable, Swift.SignedNumeric, Swift.Codable, Combine.SchedulerTimeIntervalConvertible {
|
||||
public typealias FloatLiteralType = Foundation.TimeInterval
|
||||
public typealias IntegerLiteralType = Foundation.TimeInterval
|
||||
public typealias Magnitude = Foundation.TimeInterval
|
||||
public var magnitude: Foundation.TimeInterval
|
||||
public var timeInterval: Foundation.TimeInterval {
|
||||
get
|
||||
}
|
||||
public init(integerLiteral value: Foundation.TimeInterval)
|
||||
public init(floatLiteral value: Foundation.TimeInterval)
|
||||
public init(_ timeInterval: Foundation.TimeInterval)
|
||||
public init?<T>(exactly source: T) where T : Swift.BinaryInteger
|
||||
public static func < (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
public static func * (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func + (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func - (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func *= (lhs: inout Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride)
|
||||
public static func += (lhs: inout Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride)
|
||||
public static func -= (lhs: inout Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride)
|
||||
public static func seconds(_ s: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func seconds(_ s: Swift.Double) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func milliseconds(_ ms: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func microseconds(_ us: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func nanoseconds(_ ns: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public static func == (a: Foundation.OperationQueue.SchedulerTimeType.Stride, b: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
}
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public var hashValue: Swift.Int {
|
||||
get
|
||||
}
|
||||
public func hash(into hasher: inout Swift.Hasher)
|
||||
}
|
||||
public struct SchedulerOptions {
|
||||
}
|
||||
public func schedule(options: Foundation.OperationQueue.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.OperationQueue.SchedulerTimeType, tolerance: Foundation.OperationQueue.SchedulerTimeType.Stride, options: Foundation.OperationQueue.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.OperationQueue.SchedulerTimeType, interval: Foundation.OperationQueue.SchedulerTimeType.Stride, tolerance: Foundation.OperationQueue.SchedulerTimeType.Stride, options: Foundation.OperationQueue.SchedulerOptions?, _ action: @escaping () -> Swift.Void) -> Combine.Cancellable
|
||||
public var now: Foundation.OperationQueue.SchedulerTimeType {
|
||||
get
|
||||
}
|
||||
public var minimumTolerance: Foundation.OperationQueue.SchedulerTimeType.Stride {
|
||||
get
|
||||
}
|
||||
}
|
||||
|
||||
extension RunLoop : Combine.Scheduler {
|
||||
public struct SchedulerTimeType : Swift.Strideable, Swift.Codable, Swift.Hashable {
|
||||
public var date: Foundation.Date
|
||||
public init(_ date: Foundation.Date)
|
||||
public func distance(to other: Foundation.RunLoop.SchedulerTimeType) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public func advanced(by n: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType
|
||||
public struct Stride : Swift.ExpressibleByFloatLiteral, Swift.Comparable, Swift.SignedNumeric, Swift.Codable, Combine.SchedulerTimeIntervalConvertible {
|
||||
public typealias FloatLiteralType = Foundation.TimeInterval
|
||||
public typealias IntegerLiteralType = Foundation.TimeInterval
|
||||
public typealias Magnitude = Foundation.TimeInterval
|
||||
public var magnitude: Foundation.TimeInterval
|
||||
public var timeInterval: Foundation.TimeInterval {
|
||||
get
|
||||
}
|
||||
public init(integerLiteral value: Foundation.TimeInterval)
|
||||
public init(floatLiteral value: Foundation.TimeInterval)
|
||||
public init(_ timeInterval: Foundation.TimeInterval)
|
||||
public init?<T>(exactly source: T) where T : Swift.BinaryInteger
|
||||
public static func < (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
public static func * (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func + (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func - (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func *= (lhs: inout Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride)
|
||||
public static func += (lhs: inout Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride)
|
||||
public static func -= (lhs: inout Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride)
|
||||
public static func seconds(_ s: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func seconds(_ s: Swift.Double) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func milliseconds(_ ms: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func microseconds(_ us: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func nanoseconds(_ ns: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public static func == (a: Foundation.RunLoop.SchedulerTimeType.Stride, b: Foundation.RunLoop.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
}
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public var hashValue: Swift.Int {
|
||||
get
|
||||
}
|
||||
public func hash(into hasher: inout Swift.Hasher)
|
||||
}
|
||||
public struct SchedulerOptions {
|
||||
}
|
||||
public func schedule(options: Foundation.RunLoop.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.RunLoop.SchedulerTimeType, tolerance: Foundation.RunLoop.SchedulerTimeType.Stride, options: Foundation.RunLoop.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.RunLoop.SchedulerTimeType, interval: Foundation.RunLoop.SchedulerTimeType.Stride, tolerance: Foundation.RunLoop.SchedulerTimeType.Stride, options: Foundation.RunLoop.SchedulerOptions?, _ action: @escaping () -> Swift.Void) -> Combine.Cancellable
|
||||
public var now: Foundation.RunLoop.SchedulerTimeType {
|
||||
get
|
||||
}
|
||||
public var minimumTolerance: Foundation.RunLoop.SchedulerTimeType.Stride {
|
||||
get
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
/// A subject that wraps a single value and publishes a new element whenever the value
|
||||
/// changes.
|
||||
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Max Desiatov on 24/11/2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
/// A publisher that eventually produces one value and then finishes or fails.
|
||||
public final class Future<Output, Failure>: Publisher where Failure: Error {
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 23.10.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
/// A helper class that acts like both subscriber and subscription.
|
||||
///
|
||||
/// Filter-like operators send an instance of their `Inner` class that is subclass
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 22.09.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
/// A helper class that acts like both subscriber and subscription.
|
||||
///
|
||||
/// Reduce-like operators send an instance of their `Inner` class that is subclass
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 16/09/2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
internal final class SubjectSubscriber<Downstream: Subject>
|
||||
: Subscriber,
|
||||
|
||||
@@ -67,19 +67,126 @@ public final class ObservableObjectPublisher: Publisher {
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
private let subject: PassthroughSubject<Void, Never>
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
public init() {
|
||||
subject = .init()
|
||||
private var connections = Set<Conduit>()
|
||||
|
||||
// TODO: Combine needs this for some reason
|
||||
private var identifier: ObjectIdentifier?
|
||||
|
||||
public init() {}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Void, Downstream.Failure == Never
|
||||
{
|
||||
subject.subscribe(subscriber)
|
||||
let inner = Inner(downstream: subscriber, parent: self)
|
||||
lock.lock()
|
||||
connections.insert(inner)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
|
||||
public func send() {
|
||||
subject.send()
|
||||
lock.lock()
|
||||
let connections = self.connections
|
||||
lock.unlock()
|
||||
for connection in connections {
|
||||
connection.send()
|
||||
}
|
||||
}
|
||||
|
||||
private func remove(_ conduit: Conduit) {
|
||||
lock.lock()
|
||||
connections.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension ObservableObjectPublisher {
|
||||
private class Conduit: Hashable {
|
||||
|
||||
fileprivate func send() {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
fileprivate static func == (lhs: Conduit, rhs: Conduit) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
fileprivate func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Conduit,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Void, Downstream.Failure == Never
|
||||
{
|
||||
private enum State {
|
||||
case initialized
|
||||
case active
|
||||
case terminal
|
||||
}
|
||||
|
||||
private weak var parent: ObservableObjectPublisher?
|
||||
private let downstream: Downstream
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state = State.initialized
|
||||
|
||||
init(downstream: Downstream, parent: ObservableObjectPublisher) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
downstreamLock.deallocate()
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
override func send() {
|
||||
lock.lock()
|
||||
let state = self.state
|
||||
lock.unlock()
|
||||
if state == .active {
|
||||
downstreamLock.lock()
|
||||
_ = downstream.receive()
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
if state == .initialized {
|
||||
state = .active
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
parent?.remove(self)
|
||||
}
|
||||
|
||||
var description: String { return "ObservableObjectPublisher" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any {
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
/// A subject that passes along values and completion.
|
||||
///
|
||||
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
|
||||
|
||||
@@ -0,0 +1,952 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.Merge.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 04/10/2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
// swiftlint:disable vertical_parameter_alignment
|
||||
|
||||
// MARK: - Merge methods on Publisher
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - other: Another publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge<Self, P>
|
||||
where Failure == P.Failure, Output == P.Output
|
||||
{
|
||||
return .init(self, other)
|
||||
}
|
||||
/// Combines elements from this publisher with those from three other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher
|
||||
>(with b: B,
|
||||
_ c: C) -> Publishers.Merge3<Self, B, C>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output
|
||||
{
|
||||
return .init(self, b, c)
|
||||
}
|
||||
/// Combines elements from this publisher with those from four other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D) -> Publishers.Merge4<Self, B, C, D>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output
|
||||
{
|
||||
return .init(self, b, c, d)
|
||||
}
|
||||
/// Combines elements from this publisher with those from five other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E) -> Publishers.Merge5<Self, B, C, D, E>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output
|
||||
{
|
||||
return .init(self, b, c, d, e)
|
||||
}
|
||||
/// Combines elements from this publisher with those from six other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F) -> Publishers.Merge6<Self, B, C, D, E, F>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output
|
||||
{
|
||||
return .init(self, b, c, d, e, f)
|
||||
}
|
||||
/// Combines elements from this publisher with those from seven other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - g: A seventh publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G) -> Publishers.Merge7<Self, B, C, D, E, F, G>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output
|
||||
{
|
||||
return .init(self, b, c, d, e, f, g)
|
||||
}
|
||||
/// Combines elements from this publisher with those from eight other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - g: A seventh publisher.
|
||||
/// - h: An eighth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher,
|
||||
H: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G,
|
||||
_ h: H) -> Publishers.Merge8<Self, B, C, D, E, F, G, H>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output,
|
||||
G.Failure == H.Failure, G.Output == H.Output
|
||||
{
|
||||
return .init(self, b, c, d, e, f, g, h)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher of
|
||||
/// the same type, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// - Parameter other: Another publisher of this publisher's type.
|
||||
/// - Returns: A publisher that emits an event when either upstream publisher emits
|
||||
/// an event.
|
||||
public func merge(with other: Self) -> Publishers.MergeMany<Self> {
|
||||
return .init([self, other])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Merge publishers
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the merge function to two upstream
|
||||
/// publishers.
|
||||
public struct Merge<A: Publisher,
|
||||
B: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 2)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge3<A, B, P>
|
||||
{
|
||||
return .init(a, b, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge4<A, B, Z, Y>
|
||||
{
|
||||
return .init(a, b, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge5<A, B, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, z, y, x)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W) -> Publishers.Merge6<A, B, Z, Y, X, W>
|
||||
{
|
||||
return .init(a, b, z, y, x, w)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher,
|
||||
V: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W,
|
||||
_ v: V) -> Publishers.Merge7<A, B, Z, Y, X, W, V>
|
||||
{
|
||||
return .init(a, b, z, y, x, w, v)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher,
|
||||
V: Publisher,
|
||||
U: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W,
|
||||
_ v: V,
|
||||
_ u: U) -> Publishers.Merge8<A, B, Z, Y, X, W, V, U>
|
||||
{
|
||||
return .init(a, b, z, y, x, w, v, u)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to three upstream
|
||||
/// publishers.
|
||||
public struct Merge3<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 3)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge4<A, B, C, P>
|
||||
{
|
||||
return .init(a, b, c, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge5<A, B, C, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge6<A, B, C, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, c, z, y, x)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W) -> Publishers.Merge7<A, B, C, Z, Y, X, W>
|
||||
{
|
||||
return .init(a, b, c, z, y, x, w)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher,
|
||||
V: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W,
|
||||
_ v: V) -> Publishers.Merge8<A, B, C, Z, Y, X, W, V>
|
||||
{
|
||||
return .init(a, b, c, z, y, x, w, v)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to four upstream
|
||||
/// publishers.
|
||||
public struct Merge4<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 4)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge5<A, B, C, D, P>
|
||||
{
|
||||
return .init(a, b, c, d, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge6<A, B, C, D, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, d, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge7<A, B, C, D, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, c, d, z, y, x)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W) -> Publishers.Merge8<A, B, C, D, Z, Y, X, W>
|
||||
{
|
||||
return .init(a, b, c, d, z, y, x, w)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to five upstream
|
||||
/// publishers.
|
||||
public struct Merge5<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 5)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge6<A, B, C, D, E, P>
|
||||
{
|
||||
return .init(a, b, c, d, e, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge7<A, B, C, D, E, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, d, e, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge8<A, B, C, D, E, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, c, d, e, z, y, x)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to six upstream
|
||||
/// publishers.
|
||||
public struct Merge6<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
self.f = f
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 6)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
f.subscribe(Merged.Side(index: 5, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge7<A, B, C, D, E, F, P>
|
||||
{
|
||||
return .init(a, b, c, d, e, f, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge8<A, B, C, D, E, F, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, d, e, f, z, y)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to seven upstream
|
||||
/// publishers.
|
||||
public struct Merge7<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public let g: G
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
self.f = f
|
||||
self.g = g
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 7)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
f.subscribe(Merged.Side(index: 5, merger: merged))
|
||||
g.subscribe(Merged.Side(index: 6, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge8<A, B, C, D, E, F, G, P>
|
||||
{
|
||||
return .init(a, b, c, d, e, f, g, other)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to eight upstream
|
||||
/// publishers.
|
||||
public struct Merge8<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher,
|
||||
H: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output,
|
||||
G.Failure == H.Failure, G.Output == H.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public let g: G
|
||||
|
||||
public let h: H
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G,
|
||||
_ h: H
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
self.f = f
|
||||
self.g = g
|
||||
self.h = h
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 8)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
f.subscribe(Merged.Side(index: 5, merger: merged))
|
||||
g.subscribe(Merged.Side(index: 6, merger: merged))
|
||||
h.subscribe(Merged.Side(index: 7, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct MergeMany<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let publishers: [Upstream]
|
||||
|
||||
public init(_ upstream: Upstream...) {
|
||||
self.publishers = upstream
|
||||
}
|
||||
|
||||
public init<UpstreamPublishers: Swift.Sequence>(_ upstream: UpstreamPublishers)
|
||||
where Upstream == UpstreamPublishers.Element
|
||||
{
|
||||
publishers = Array(upstream)
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: publishers.count)
|
||||
for (i, upstream) in publishers.enumerated() {
|
||||
upstream.subscribe(Merged.Side(index: i, merger: merged))
|
||||
}
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge(with other: Upstream) -> Publishers.MergeMany<Upstream> {
|
||||
var newPublishers = publishers
|
||||
newPublishers.append(other)
|
||||
return .init(newPublishers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable conformances
|
||||
|
||||
extension Publishers.Merge: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable {}
|
||||
|
||||
extension Publishers.Merge3: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable {}
|
||||
|
||||
extension Publishers.Merge4: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable {}
|
||||
|
||||
extension Publishers.Merge5: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable {}
|
||||
|
||||
extension Publishers.Merge6: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable,
|
||||
F: Equatable {}
|
||||
|
||||
extension Publishers.Merge7: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable,
|
||||
F: Equatable,
|
||||
G: Equatable {}
|
||||
|
||||
extension Publishers.Merge8: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable,
|
||||
F: Equatable,
|
||||
G: Equatable,
|
||||
H: Equatable {}
|
||||
|
||||
extension Publishers.MergeMany: Equatable
|
||||
where
|
||||
Upstream: Equatable {}
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Publishers.AssertNoFailure.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes
|
||||
/// all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but
|
||||
/// do not impact performance of shipping code.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - prefix: A string used at the beginning of the fatal error message.
|
||||
/// - file: A filename used in the error message. This defaults to `#file`.
|
||||
/// - line: A line number used in the error message. This defaults to `#line`.
|
||||
/// - Returns: A publisher that raises a fatal error when its upstream publisher
|
||||
/// fails.
|
||||
public func assertNoFailure(_ prefix: String = "",
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) -> Publishers.AssertNoFailure<Self> {
|
||||
return .init(upstream: self, prefix: prefix, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that raises a fatal error upon receiving any failure, and otherwise
|
||||
/// republishes all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but
|
||||
/// do not impact performance of shipping code.
|
||||
public struct AssertNoFailure<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The string used at the beginning of the fatal error message.
|
||||
public let prefix: String
|
||||
|
||||
/// The filename used in the error message.
|
||||
public let file: StaticString
|
||||
|
||||
/// The line number used in the error message.
|
||||
public let line: UInt
|
||||
|
||||
public init(upstream: Upstream, prefix: String, file: StaticString, line: UInt) {
|
||||
self.upstream = upstream
|
||||
self.prefix = prefix
|
||||
self.file = file
|
||||
self.line = line
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber,
|
||||
prefix: prefix,
|
||||
file: file,
|
||||
line: line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.AssertNoFailure {
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Never
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let prefix: String
|
||||
|
||||
private let file: StaticString
|
||||
|
||||
private let line: UInt
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(downstream: Downstream, prefix: String, file: StaticString, line: UInt) {
|
||||
self.downstream = downstream
|
||||
self.prefix = prefix
|
||||
self.file = file
|
||||
self.line = line
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
let prefix = self.prefix.isEmpty ? "" : self.prefix + ": "
|
||||
fatalError("\(prefix)\(error)", file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "AssertNoFailure" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("file", file),
|
||||
("line", line),
|
||||
("prefix", prefix)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension ConnectablePublisher {
|
||||
|
||||
/// Automates the process of connecting or disconnecting from this connectable
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// Publishers.CollectByCount.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Collects up to the specified number of elements, and then emits a single array of
|
||||
/// the collection.
|
||||
///
|
||||
/// If the upstream publisher finishes before filling the buffer, this publisher sends
|
||||
/// an array of all the items it has received. This may be fewer than `count`
|
||||
/// elements.
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
/// Note: When this publisher receives a request for `.max(n)` elements, it requests
|
||||
/// `.max(count * n)` from the upstream publisher.
|
||||
///
|
||||
/// - Parameter count: The maximum number of received elements to buffer before
|
||||
/// publishing.
|
||||
/// - Returns: A publisher that collects up to the specified number of elements, and
|
||||
/// then publishes them as an array.
|
||||
public func collect(_ count: Int) -> Publishers.CollectByCount<Self> {
|
||||
return .init(upstream: self, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that buffers a maximum number of items.
|
||||
public struct CollectByCount<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = [Upstream.Output]
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The maximum number of received elements to buffer before publishing.
|
||||
public let count: Int
|
||||
|
||||
public init(upstream: Upstream, count: Int) {
|
||||
self.upstream = upstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, count: count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.CollectByCount: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.CollectByCount {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == [Upstream.Output],
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let count: Int
|
||||
|
||||
private var buffer: [Input] = []
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
private var finished = false
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
init(downstream: Downstream, count: Int) {
|
||||
self.downstream = downstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if subscription == nil {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
buffer.append(input)
|
||||
guard buffer.count == count else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let output = self.buffer
|
||||
self.buffer = []
|
||||
lock.unlock()
|
||||
return downstream.receive(output) * count
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
subscription = nil
|
||||
finished = true
|
||||
switch completion {
|
||||
case .finished:
|
||||
if buffer.isEmpty {
|
||||
lock.unlock()
|
||||
} else {
|
||||
let buffer = self.buffer
|
||||
self.buffer = []
|
||||
lock.unlock()
|
||||
_ = downstream.receive(buffer)
|
||||
}
|
||||
case .failure:
|
||||
buffer = []
|
||||
lock.unlock()
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
if let subscription = self.subscription {
|
||||
lock.unlock()
|
||||
subscription.request(demand * count)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
if let subscription = self.subscription {
|
||||
buffer = []
|
||||
finished = true
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "CollectByCount" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("upstreamSubscription", subscription as Any),
|
||||
("buffer", buffer),
|
||||
("count", count)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -145,9 +145,7 @@ extension Publishers.Concatenate {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
// ??? This lock is non-recursive in Combine, but it should be!
|
||||
// (FB7404824 if Apple folks are watching)
|
||||
private let downstreamLock = UnfairLock.allocate()
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, suffix: Suffix) {
|
||||
self.downstream = downstream
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Евгений Богомолов on 07/09/2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Delays delivery of all output to the downstream receiver by a specified amount
|
||||
@@ -104,7 +100,7 @@ extension Publishers.Delay {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state: State
|
||||
private let downstreamLock = UnfairLock.allocate()
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(_ publisher: Delay, downstream: Downstream) {
|
||||
state = .ready(publisher, downstream)
|
||||
@@ -115,13 +111,7 @@ extension Publishers.Delay {
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
private func schedule(_ delay: Delay,
|
||||
immediate: Bool,
|
||||
work: @escaping () -> Void) {
|
||||
if immediate {
|
||||
delay.scheduler.schedule(options: delay.options, work)
|
||||
return
|
||||
}
|
||||
private func schedule(_ delay: Delay, work: @escaping () -> Void) {
|
||||
delay
|
||||
.scheduler
|
||||
.schedule(after: delay.scheduler.now.advanced(by: delay.interval),
|
||||
@@ -139,18 +129,6 @@ extension Publishers.Delay {
|
||||
}
|
||||
state = .subscribed(delay, downstream, subscription)
|
||||
lock.unlock()
|
||||
schedule(delay, immediate: true) { [weak self] in
|
||||
self?.scheduledReceive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
@@ -163,8 +141,8 @@ extension Publishers.Delay {
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
schedule(delay, immediate: false) { [weak self] in
|
||||
self?.scheduledReceive(input, downstream: downstream)
|
||||
schedule(delay) {
|
||||
self.scheduledReceive(input, downstream: downstream)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
@@ -193,8 +171,8 @@ extension Publishers.Delay {
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
schedule(delay, immediate: false) { [weak self] in
|
||||
self?.scheduledReceive(completion: completion, downstream: downstream)
|
||||
schedule(delay) {
|
||||
self.scheduledReceive(completion: completion, downstream: downstream)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sven Weidauer on 03.10.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
/// Omits the specified number of elements before republishing subsequent elements.
|
||||
///
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
//
|
||||
// Publishers.DropUntilOutput.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ignores elements from the upstream publisher until it receives an element from
|
||||
/// a second publisher.
|
||||
///
|
||||
/// This publisher requests a single value from the upstream publisher, and it ignores
|
||||
/// (drops) all elements from that publisher until the upstream publisher produces
|
||||
/// a value. After the `other` publisher produces an element, this publisher cancels
|
||||
/// its subscription to the `other` publisher, and allows events from the `upstream`
|
||||
/// publisher to pass through.
|
||||
/// After this publisher receives a subscription from the upstream publisher, it
|
||||
/// passes through backpressure requests from downstream to the upstream publisher.
|
||||
/// If the upstream publisher acts on those requests before the other publisher
|
||||
/// produces an item, this publisher drops the elements it receives from the upstream
|
||||
/// publisher.
|
||||
///
|
||||
/// - Parameter publisher: A publisher to monitor for its first emitted element.
|
||||
/// - Returns: A publisher that drops elements from the upstream publisher until the
|
||||
/// `other` publisher produces a value.
|
||||
public func drop<Other: Publisher>(
|
||||
untilOutputFrom publisher: Other
|
||||
) -> Publishers.DropUntilOutput<Self, Other> where Failure == Other.Failure {
|
||||
return .init(upstream: self, other: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that ignores elements from the upstream publisher until it receives
|
||||
/// an element from second publisher.
|
||||
public struct DropUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher
|
||||
where Upstream.Failure == Other.Failure
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A publisher to monitor for its first emitted element.
|
||||
public let other: Other
|
||||
|
||||
/// Creates a publisher that ignores elements from the upstream publisher until
|
||||
/// it receives an element from another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: A publisher to drop elements from while waiting for another
|
||||
/// publisher to emit elements.
|
||||
/// - other: A publisher to monitor for its first emitted element.
|
||||
public init(upstream: Upstream, other: Other) {
|
||||
self.upstream = upstream
|
||||
self.other = other
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Other.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber)
|
||||
other.subscribe(Inner.OtherSubscriber(inner: inner))
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.DropUntilOutput: Equatable
|
||||
where Upstream: Equatable, Other: Equatable {}
|
||||
|
||||
extension Publishers.DropUntilOutput {
|
||||
fileprivate final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var triggered = false
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
private var otherSubscription: Subscription?
|
||||
|
||||
private var otherFinished = false
|
||||
|
||||
private var cancelled = false
|
||||
|
||||
init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard upstreamSubscription == nil && !cancelled else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = subscription
|
||||
if pendingDemand > 0 {
|
||||
lock.unlock()
|
||||
subscription.request(pendingDemand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if !triggered || cancelled {
|
||||
pendingDemand -= 1
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
if cancelled {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
private func receiveOther(subscription: Subscription) {
|
||||
// Combine doesn't lock here
|
||||
guard otherSubscription == nil else {
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
otherSubscription = subscription
|
||||
subscription.request(.max(1))
|
||||
}
|
||||
|
||||
private func receiveOther(_ input: Other.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
triggered = true
|
||||
otherSubscription = nil
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
private func receiveOther(completion: Subscribers.Completion<Other.Failure>) {
|
||||
lock.lock()
|
||||
if triggered {
|
||||
otherSubscription = nil
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
otherFinished = true
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
self.upstreamSubscription = nil
|
||||
lock.unlock()
|
||||
upstreamSubscription.cancel()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
pendingDemand += demand
|
||||
if let subscription = upstreamSubscription {
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let upstreamSubscription = self.upstreamSubscription
|
||||
let otherSubscription = self.otherSubscription
|
||||
self.upstreamSubscription = nil
|
||||
self.otherSubscription = nil
|
||||
cancelled = true
|
||||
lock.unlock()
|
||||
|
||||
upstreamSubscription?.cancel()
|
||||
otherSubscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "DropUntilOutput" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.DropUntilOutput.Inner {
|
||||
fileprivate struct OtherSubscriber
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
let inner: Publishers.DropUntilOutput<Upstream, Other>.Inner<Downstream>
|
||||
|
||||
var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receiveOther(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Other.Output) -> Subscribers.Demand {
|
||||
return inner.receiveOther(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Other.Failure>) {
|
||||
inner.receiveOther(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "DropUntilOutput" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Omits elements from the upstream publisher until a given closure returns false,
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
// Created by Eric Patey on 16.08.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
/// Transforms all elements from an upstream publisher into a new or existing
|
||||
/// publisher.
|
||||
@@ -92,10 +88,9 @@ extension Publishers.FlatMap {
|
||||
/// by the `downstreamLock`.
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
// Must be recursive lock. Probably a bug in Combine.
|
||||
/// All the calls to the downstream subscriber should be made with this lock
|
||||
/// acquired.
|
||||
private let downstreamLock = UnfairLock.allocate()
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Performs the specified closures when publisher events occur.
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
// Created by Eric Patey on 16.08.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ingores all upstream elements, but passes along a completion
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Anton Nazarov on 25.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms all elements from the upstream publisher with a provided closure.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Measures and emits the time interval between events received from an upstream
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.Merge.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 04/10/2019.
|
||||
//
|
||||
%{
|
||||
from gyb_opencombine_support import (
|
||||
suffix_variadic,
|
||||
list_with_suffix_variadic,
|
||||
indent
|
||||
)
|
||||
|
||||
import string
|
||||
|
||||
instantiations = [(2, 'two', 'A second'),
|
||||
(3, 'three', 'A third'),
|
||||
(4, 'four', 'A fourth'),
|
||||
(5, 'five', 'A fifth'),
|
||||
(6, 'six', 'A sixth'),
|
||||
(7, 'seven', 'A seventh'),
|
||||
(8, 'eight', 'An eighth')]
|
||||
|
||||
def make_publisher_name(arity):
|
||||
return suffix_variadic('Merge', arity, arity - 1)
|
||||
|
||||
def make_upstream_types(arity, start=0):
|
||||
return [str(c) for c in string.ascii_uppercase[start:arity]]
|
||||
|
||||
def make_upstream_types_reversed(arity):
|
||||
return [str(c) for c in reversed(string.ascii_uppercase)][:arity]
|
||||
|
||||
def make_upstream_generic_constraints(upstream_types, first_is_self=False):
|
||||
|
||||
format_string = '{0}Failure == {1}.Failure, {0}Output == {1}.Output'
|
||||
|
||||
def format(i):
|
||||
return format_string.format(upstream_types[i] + '.',
|
||||
upstream_types[i + 1])
|
||||
|
||||
result = [format(i) for i in range(len(upstream_types) - 1)]
|
||||
|
||||
if first_is_self:
|
||||
result.insert(0, format_string.format('', upstream_types[0]))
|
||||
|
||||
return result
|
||||
|
||||
def declare_merge_method(arg_count, arity, indent_spaces_count):
|
||||
assert(arg_count <= arity - 1)
|
||||
is_specialization = arg_count < arity - 1
|
||||
|
||||
declaration_format = """\
|
||||
public func merge<
|
||||
{}
|
||||
>(with {}) -> Publishers.{}<{}>\
|
||||
"""
|
||||
|
||||
where_clause_format = '\n where {}'
|
||||
|
||||
if arg_count == 1:
|
||||
upstream_types = ['P']
|
||||
elif is_specialization:
|
||||
upstream_types = make_upstream_types_reversed(arg_count)
|
||||
else:
|
||||
upstream_types = make_upstream_types(arg_count + 1, 1)
|
||||
|
||||
method_generic_params = \
|
||||
[upstream_type + ': Publisher' for upstream_type in upstream_types]
|
||||
|
||||
cs_method_generic_params = \
|
||||
(',\n ').join(method_generic_params)
|
||||
|
||||
method_args = ['other: P'] \
|
||||
if arg_count == 1 else ['{}: {}'.format(upstream_type.lower(), upstream_type) \
|
||||
for upstream_type in upstream_types]
|
||||
|
||||
cs_method_args = ',\n _ '.join(method_args)
|
||||
|
||||
publisher_name = make_publisher_name(arity)
|
||||
|
||||
self_generic_params = make_upstream_types(arity - arg_count) \
|
||||
if is_specialization else ['Self']
|
||||
|
||||
publisher_generic_params = self_generic_params + upstream_types
|
||||
|
||||
cs_publisher_generic_params = ', '.join(publisher_generic_params)
|
||||
|
||||
generic_constraints = make_upstream_generic_constraints(upstream_types, True)
|
||||
|
||||
cs_generic_constraints = \
|
||||
',\n '.join(generic_constraints)
|
||||
|
||||
declaration = declaration_format.format(cs_method_generic_params,
|
||||
cs_method_args,
|
||||
publisher_name,
|
||||
cs_publisher_generic_params)
|
||||
|
||||
if not is_specialization:
|
||||
declaration += where_clause_format.format(cs_generic_constraints)
|
||||
|
||||
return indent(declaration, indent_spaces_count)
|
||||
}%
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
// swiftlint:disable vertical_parameter_alignment
|
||||
|
||||
// MARK: - Merge methods on Publisher
|
||||
|
||||
extension Publisher {
|
||||
|
||||
% for arity, _, _ in instantiations:
|
||||
%
|
||||
% doc_cardinal = 'another publisher' \
|
||||
% if arity == 2 else (instantiations[arity - 2][1] + ' other publishers')
|
||||
% argument_names = ['other'] \
|
||||
% if arity == 2 else [upstream_type.lower() \
|
||||
% for upstream_type in make_upstream_types(arity, 1)]
|
||||
/// Combines elements from this publisher with those from ${doc_cardinal},
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity - 1):
|
||||
% param_doc = 'Another' if arity == 2 else instantiations[i][2]
|
||||
/// - ${argument_names[i]}: ${param_doc} publisher.
|
||||
% end
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
${declare_merge_method(arity - 1, arity, 4)}
|
||||
{
|
||||
return .init(self, ${', '.join(argument_names)})
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher of
|
||||
/// the same type, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// - Parameter other: Another publisher of this publisher's type.
|
||||
/// - Returns: A publisher that emits an event when either upstream publisher emits
|
||||
/// an event.
|
||||
public func merge(with other: Self) -> Publishers.MergeMany<Self> {
|
||||
return .init([self, other])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Merge publishers
|
||||
|
||||
extension Publishers {
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% upstream_types = make_upstream_types(arity)
|
||||
%
|
||||
% upstream_generic_params = \
|
||||
% [upstream_type + ': Publisher' for upstream_type in upstream_types]
|
||||
%
|
||||
% cs_upstream_generic_params = \
|
||||
% (',\n' + (19 + len(publisher_name)) * ' ').join(upstream_generic_params)
|
||||
%
|
||||
% upstream_generic_constraints = \
|
||||
% make_upstream_generic_constraints(upstream_types)
|
||||
%
|
||||
% cs_upstream_generic_constraints = \
|
||||
% ',\n '.join(upstream_generic_constraints)
|
||||
%
|
||||
% init_args = ['_ {}: {}'.format(upstream_type.lower(), upstream_type) \
|
||||
% for upstream_type in upstream_types]
|
||||
% cs_init_args = ',\n '.join(init_args)
|
||||
%
|
||||
% self_fields = [upstream_type.lower() for upstream_type in upstream_types]
|
||||
|
||||
/// A publisher created by applying the merge function to ${cardinal} upstream
|
||||
/// publishers.
|
||||
public struct ${publisher_name}<${cs_upstream_generic_params}>: Publisher
|
||||
where ${cs_upstream_generic_constraints}
|
||||
{
|
||||
public typealias Output = ${upstream_types[0]}.Output
|
||||
|
||||
public typealias Failure = ${upstream_types[0]}.Failure
|
||||
% for upstream_type in upstream_types:
|
||||
|
||||
public let ${upstream_type.lower()}: ${upstream_type}
|
||||
% end
|
||||
|
||||
public init(
|
||||
${cs_init_args}
|
||||
) {
|
||||
% for self_field in self_fields:
|
||||
self.${self_field} = ${self_field}
|
||||
% end
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where ${upstream_types[0]}.Failure == Downstream.Failure,
|
||||
${upstream_types[0]}.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: ${arity})
|
||||
% for i in range(len(self_fields)):
|
||||
${self_fields[i]}.subscribe(Merged.Side(index: ${i}, merger: merged))
|
||||
% end
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
% for i in range(len(instantiations) + 1 - arity):
|
||||
% argument_names = ['other'] \
|
||||
% if i == 0 else [upstream_type.lower() \
|
||||
% for upstream_type in make_upstream_types_reversed(i + 1)]
|
||||
%
|
||||
|
||||
${declare_merge_method(i + 1, arity + i + 1, 8)}
|
||||
{
|
||||
return .init(${', '.join(self_fields + argument_names)})
|
||||
}
|
||||
% end
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct MergeMany<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let publishers: [Upstream]
|
||||
|
||||
public init(_ upstream: Upstream...) {
|
||||
self.publishers = upstream
|
||||
}
|
||||
|
||||
public init<UpstreamPublishers: Swift.Sequence>(_ upstream: UpstreamPublishers)
|
||||
where Upstream == UpstreamPublishers.Element
|
||||
{
|
||||
publishers = Array(upstream)
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: publishers.count)
|
||||
for (i, upstream) in publishers.enumerated() {
|
||||
upstream.subscribe(Merged.Side(index: i, merger: merged))
|
||||
}
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge(with other: Upstream) -> Publishers.MergeMany<Upstream> {
|
||||
var newPublishers = publishers
|
||||
newPublishers.append(other)
|
||||
return .init(newPublishers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable conformances
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% upstream_types = make_upstream_types(arity)
|
||||
%
|
||||
% constraints = [upstream_type + ': Equatable' for upstream_type in upstream_types]
|
||||
% cs_constraints = ',\n'.join(constraints)
|
||||
% cs_constraints = indent(cs_constraints, 8)
|
||||
%
|
||||
|
||||
extension Publishers.${publisher_name}: Equatable
|
||||
where
|
||||
${cs_constraints} {}
|
||||
% end
|
||||
|
||||
extension Publishers.MergeMany: Equatable
|
||||
where
|
||||
Upstream: Equatable {}
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 14.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Applies a closure to create a subject that delivers elements to subscribers.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 24.10.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements up to the specified maximum count.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Prints log messages for all publishing events.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 02.12.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
/// Specifies the scheduler on which to receive elements from the publisher.
|
||||
///
|
||||
@@ -101,7 +97,7 @@ extension Publishers.ReceiveOn {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state: State
|
||||
private let downstreamLock = UnfairLock.allocate()
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
init(_ receiveOn: ReceiveOn, downstream: Downstream) {
|
||||
state = .ready(receiveOn, downstream)
|
||||
@@ -121,18 +117,6 @@ extension Publishers.ReceiveOn {
|
||||
}
|
||||
state = .subscribed(receiveOn, downstream, subscription)
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
|
||||
self?.scheduledReceive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// Publishers.ReplaceEmpty.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Joe Spadafora on 12/10/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces an empty stream with the provided element.
|
||||
///
|
||||
/// If the upstream publisher finishes without producing any elements,
|
||||
/// this publisher emits the provided element, then finishes normally.
|
||||
/// - Parameter output: An element to emit when the upstream publisher
|
||||
/// finishes without emitting any elements.
|
||||
/// - Returns: A publisher that replaces an empty stream with
|
||||
/// the provided output element.
|
||||
public func replaceEmpty(with output: Output) -> Publishers.ReplaceEmpty<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that replaces an empty stream with a provided element.
|
||||
public struct ReplaceEmpty<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The element to deliver when the upstream publisher finishes
|
||||
/// without delivering any elements.
|
||||
public let output: Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream, output: Output) {
|
||||
self.upstream = upstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, output: output)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.ReplaceEmpty: Equatable
|
||||
where Upstream: Equatable, Upstream.Output: Equatable {}
|
||||
|
||||
extension Publishers.ReplaceEmpty {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let output: Output
|
||||
private let downstream: Downstream
|
||||
|
||||
private var receivedUpstream = false
|
||||
private var lock = UnfairLock.allocate()
|
||||
private var downstreamRequested = false
|
||||
private var finishedWithoutUpstream = false
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
fileprivate init(downstream: Downstream, output: Output) {
|
||||
self.downstream = downstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
receivedUpstream = true
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
if receivedUpstream {
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
return
|
||||
}
|
||||
switch completion {
|
||||
case .finished:
|
||||
if downstreamRequested {
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: completion)
|
||||
return
|
||||
}
|
||||
finishedWithoutUpstream = true
|
||||
lock.unlock()
|
||||
case .failure:
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
downstreamRequested = true
|
||||
if finishedWithoutUpstream {
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
return
|
||||
}
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "ReplaceEmpty" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Bogdan Vlad on 8/29/19.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
/// Replaces any errors in the stream with the provided element.
|
||||
///
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
// Created by Eric Patey on 26.08.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms elements from the upstream publisher by providing the current element
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 19.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes a given sequence of elements.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 02.12.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Specifies the scheduler on which to perform subscribe, cancel, and request
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// Publishers._Merged.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publishers {
|
||||
// swiftlint:disable:next type_name
|
||||
internal final class _Merged<Input, Failure, Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Input, Downstream.Failure == Failure
|
||||
{
|
||||
private let downstream: Downstream
|
||||
private var demand = Subscribers.Demand.none // 0x78
|
||||
private var terminated = false // 0x80
|
||||
private let count: Int // 0x88
|
||||
private var upstreamFinished = 0 // 0x90
|
||||
private var finished = false // 0x98
|
||||
|
||||
// TODO: The size of these arrays always stays the same.
|
||||
// Maybe we can leverage ManagedBuffer/ManagedBufferPointer here
|
||||
// to avoid additional allocations.
|
||||
private var subscriptions: [Subscription?] // 0xA0
|
||||
private var buffers: [Input?] // 0xA8
|
||||
|
||||
private let lock = UnfairLock.allocate() // 0xB0
|
||||
private let downstreamLock = UnfairLock.allocate() // 0xB8
|
||||
private var recursive = false // 0xC0
|
||||
private var pending = Subscribers.Demand.none // 0xC8
|
||||
|
||||
internal init(downstream: Downstream, count: Int) {
|
||||
self.downstream = downstream
|
||||
self.count = count
|
||||
self.subscriptions = Array(repeating: nil, count: count)
|
||||
self.buffers = Array(repeating: nil, count: count)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
private func receive(subscription: Subscription, _ index: Int) {
|
||||
lock.lock()
|
||||
guard subscriptions[index] == nil else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
subscriptions[index] = subscription
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
subscription.request(demand == .unlimited ? .unlimited : .max(1))
|
||||
}
|
||||
|
||||
private func receive(_ input: Input, _ index: Int) -> Subscribers.Demand {
|
||||
func lockedSendValueDownstream() -> Subscribers.Demand {
|
||||
recursive = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
recursive = false
|
||||
return newDemand
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
if demand == .unlimited {
|
||||
let newDemand = lockedSendValueDownstream()
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
if demand == .none {
|
||||
buffers[index] = input
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
demand -= 1
|
||||
let newDemand = lockedSendValueDownstream()
|
||||
demand += newDemand + pending
|
||||
pending = .none
|
||||
lock.unlock()
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
private func receive(completion: Subscribers.Completion<Failure>, _ index: Int) {
|
||||
func lockedSendCompletionDownstream() {
|
||||
recursive = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
recursive = false
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
upstreamFinished += 1
|
||||
subscriptions[index] = nil
|
||||
// TODO: Test both conditions.
|
||||
// When receiving subscription twice, the second time
|
||||
// upstreamFinished != count
|
||||
guard upstreamFinished == count,
|
||||
subscriptions.allSatisfy({ $0 == nil }) else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
lockedSendCompletionDownstream()
|
||||
lock.unlock()
|
||||
case .failure:
|
||||
if terminated {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
terminated = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = Array(repeating: nil, count: subscriptions.count)
|
||||
lock.unlock()
|
||||
for (i, subscription) in subscriptions.enumerated() where i != index {
|
||||
subscription?.cancel()
|
||||
}
|
||||
lock.lock()
|
||||
lockedSendCompletionDownstream()
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
// TODO: Test all conditions
|
||||
if terminated || finished || demand == .none || self.demand == .unlimited {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if recursive {
|
||||
pending += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if demand == .unlimited {
|
||||
// loc_6a5b1
|
||||
self.demand = .unlimited
|
||||
}
|
||||
|
||||
// TODO: Unimplemented
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
// TODO: Unimplemented
|
||||
}
|
||||
|
||||
internal var description: String { return "Merge" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers._Merged {
|
||||
internal struct Side
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private let index: Int
|
||||
private let merger: Publishers._Merged<Input, Failure, Downstream>
|
||||
|
||||
internal let combineIdentifier = CombineIdentifier()
|
||||
|
||||
internal init(index: Int,
|
||||
merger: Publishers._Merged<Input, Failure, Downstream>) {
|
||||
self.index = index
|
||||
self.merger = merger
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
merger.receive(subscription: subscription, index)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return merger.receive(input, index)
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
merger.receive(completion: completion, index)
|
||||
}
|
||||
|
||||
internal var description: String { return "Merge" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(
|
||||
("parentSubscription", merger.combineIdentifier)
|
||||
)
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 12.11.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
/// A publisher that allows for recording a series of inputs and a completion for later
|
||||
/// playback to each subscriber.
|
||||
public struct Record<Output, Failure: Error>: Publisher {
|
||||
|
||||
@@ -330,6 +330,7 @@ extension Subscribers {
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
|
||||
/// any integer.
|
||||
@inlinable
|
||||
public static func != (lhs: Demand, rhs: Int) -> Bool {
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
@@ -340,6 +341,7 @@ extension Subscribers {
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
|
||||
/// integer.
|
||||
@inlinable
|
||||
public static func == (lhs: Int, rhs: Demand) -> Bool {
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
@@ -350,6 +352,7 @@ extension Subscribers {
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
|
||||
/// any integer.
|
||||
@inlinable
|
||||
public static func != (lhs: Int, rhs: Demand) -> Bool {
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
@@ -358,8 +361,13 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public static func == (lhs: Demand, rhs: Demand) -> Bool {
|
||||
return lhs.rawValue == rhs.rawValue
|
||||
}
|
||||
|
||||
/// Returns the number of requested values, or `nil` if `.unlimited`.
|
||||
public var max: Int? {
|
||||
@inlinable public var max: Int? {
|
||||
if self == .unlimited {
|
||||
return nil
|
||||
} else {
|
||||
|
||||
@@ -50,8 +50,12 @@ extension DispatchQueue {
|
||||
/// - Parameter other: Another dispatch queue time.
|
||||
/// - Returns: The time interval between this time and the provided time.
|
||||
public func distance(to other: SchedulerTimeType) -> Stride {
|
||||
let start = dispatchTime.rawValue
|
||||
let end = other.dispatchTime.rawValue
|
||||
return .nanoseconds(
|
||||
dispatchTime.rawValue.distance(to: other.dispatchTime.rawValue)
|
||||
end >= start
|
||||
? Int(Int64(bitPattern: end) - Int64(bitPattern: start))
|
||||
: -Int(Int64(bitPattern: start) - Int64(bitPattern: end))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,7 +66,9 @@ extension DispatchQueue {
|
||||
/// - Returns: A dispatch queue time advanced by the given
|
||||
/// interval from this instance’s time.
|
||||
public func advanced(by stride: Stride) -> SchedulerTimeType {
|
||||
return .init(dispatchTime + stride.timeInterval)
|
||||
return stride.magnitude == .max
|
||||
? .init(.distantFuture)
|
||||
: .init(dispatchTime + stride.timeInterval)
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
@@ -125,13 +131,52 @@ extension DispatchQueue {
|
||||
self = .microseconds(microseconds)
|
||||
case .nanoseconds(let nanoseconds):
|
||||
self = .nanoseconds(nanoseconds)
|
||||
// This dance is to avoid the warning 'default will never be executed'
|
||||
// on non-Darwin platforms.
|
||||
// There really shouldn't be a warning.
|
||||
// See https://forums.swift.org/t/unknown-default-produces-a-warning-on-linux-with-non-frozen-enum/31687
|
||||
//
|
||||
// Thanks to Jeremy David Giesbrecht for suggesting this workaround.
|
||||
#if canImport(Darwin)
|
||||
case .never:
|
||||
fallthrough
|
||||
@unknown default:
|
||||
self = .nanoseconds(.max)
|
||||
@unknown default:
|
||||
self.init(__guessFromUnknown: timeInterval)
|
||||
#else
|
||||
default:
|
||||
if case .never = timeInterval {
|
||||
self = .nanoseconds(.max)
|
||||
} else {
|
||||
self.init(__guessFromUnknown: timeInterval)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public // testable
|
||||
init(__guessFromUnknown timeInterval: DispatchTimeInterval) {
|
||||
// Let's take some reference time,
|
||||
// add `timeInterval` to it, take the `rawValue` from the result
|
||||
// and subtract the `rawValue` of the reference time.
|
||||
//
|
||||
// We won't be able to provide the exact implementation though,
|
||||
// because something will definitely overflow.
|
||||
//
|
||||
// However, we can try to support as wide a range of values
|
||||
// as possible.
|
||||
//
|
||||
// By trial and error I got that the `rawValue` of `UInt64.max / 13`
|
||||
// gives us propably the widest range of supported values:
|
||||
// from `Int.min / 6.5` to `Int.max / 2.889` nanoseconds.
|
||||
// That's with Int being 64 bits. Since here only UInt64 can overflow,
|
||||
// when Int is 32 bits, we don't have this issue.
|
||||
// It should be more than enough.
|
||||
|
||||
let referenceTime = DispatchTime(uptimeNanoseconds: .max / 13)
|
||||
self = SchedulerTimeType(referenceTime)
|
||||
.distance(to: SchedulerTimeType(referenceTime + timeInterval))
|
||||
}
|
||||
|
||||
/// Creates a dispatch queue time interval from a floating-point
|
||||
/// seconds value.
|
||||
///
|
||||
@@ -191,15 +236,15 @@ extension DispatchQueue {
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value * 1_000_000_000)
|
||||
return Stride(magnitude: clampedIntProduct(value, 1_000_000_000))
|
||||
}
|
||||
|
||||
public static func milliseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value * 1_000_000)
|
||||
return Stride(magnitude: clampedIntProduct(value, 1_000_000))
|
||||
}
|
||||
|
||||
public static func microseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value * 1_000)
|
||||
return Stride(magnitude: clampedIntProduct(value, 1_000))
|
||||
}
|
||||
|
||||
public static func nanoseconds(_ value: Int) -> Stride {
|
||||
@@ -336,3 +381,18 @@ extension DispatchQueue: OpenCombine.Scheduler {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// This function is taken from swift-corlibs-libdispatch:
|
||||
// https://github.com/apple/swift-corelibs-libdispatch/blob/c992dacf3ca114806e6ac9ffc9113b19255be9fe/src/swift/Time.swift#L134-L144
|
||||
//
|
||||
// Returns m1 * m2, clamped to the range [Int.min, Int.max].
|
||||
// Because of the way this function is used, we can always assume
|
||||
// that m2 > 0.
|
||||
private func clampedIntProduct(_ lhs: Int, _ rhs: Int) -> Int {
|
||||
assert(rhs > 0, "multiplier must be positive")
|
||||
let (result, overflow) = lhs.multipliedReportingOverflow(by: rhs)
|
||||
if overflow {
|
||||
return lhs > 0 ? .max : .min
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Locking.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
import OpenCombine
|
||||
|
||||
internal typealias UnfairLock = __UnfairLock
|
||||
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Violations.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 13.12.2019.
|
||||
//
|
||||
|
||||
import OpenCombine
|
||||
|
||||
extension Subscribers.Demand {
|
||||
internal func assertNonZero(file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
if self == .none {
|
||||
fatalError("API Violation: demand must not be zero", file: file, line: line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// JSONEncoder.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.10.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension JSONEncoder: TopLevelEncoder {
|
||||
public typealias Output = Data
|
||||
}
|
||||
|
||||
extension JSONDecoder: TopLevelDecoder {
|
||||
public typealias Input = Data
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
//
|
||||
// NotificationCenter.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.10.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension NotificationCenter {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Foundation are imported.
|
||||
///
|
||||
/// Foundation extends `NotificationCenter` with new methods and nested types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `NotificationCenter.Publisher`,
|
||||
/// because Swift is unable to understand which `Publisher`
|
||||
/// you're referring to — the one declared in Foundation or in OpenCombine.
|
||||
///
|
||||
/// So you have to write `NotificationCenter.OCombine.Publisher`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine {
|
||||
|
||||
public let center: NotificationCenter
|
||||
|
||||
public init(_ center: NotificationCenter) {
|
||||
self.center = center
|
||||
}
|
||||
|
||||
/// A publisher that emits elements when broadcasting notifications.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
public typealias Output = Notification
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The notification center this publisher uses as a source.
|
||||
public let center: NotificationCenter
|
||||
|
||||
/// The name of notifications published by this publisher.
|
||||
public let name: Notification.Name
|
||||
|
||||
/// The object posting the named notfication.
|
||||
public let object: AnyObject?
|
||||
|
||||
/// Creates a publisher that emits events when broadcasting notifications.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - center: The notification center to publish notifications for.
|
||||
/// - name: The name of the notification to publish.
|
||||
/// - object: The object posting the named notfication. If `nil`,
|
||||
/// the publisher emits elements for any object producing a notification
|
||||
/// with the given name.
|
||||
public init(center: NotificationCenter,
|
||||
name: Notification.Name,
|
||||
object: AnyObject? = nil) {
|
||||
self.center = center
|
||||
self.name = name
|
||||
self.object = object
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Never, Downstream.Input == Notification
|
||||
{
|
||||
let subscription = Notification.Subscription(center: center,
|
||||
name: name,
|
||||
object: object,
|
||||
downstream: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a publisher that emits events when broadcasting notifications.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: The name of the notification to publish.
|
||||
/// - object: The object posting the named notfication. If `nil`, the publisher
|
||||
/// emits elements for any object producing a notification with the given
|
||||
/// name.
|
||||
/// - Returns: A publisher that emits events when broadcasting notifications.
|
||||
public func publisher(for name: Notification.Name,
|
||||
object: AnyObject? = nil) -> Publisher {
|
||||
return .init(center: center, name: name, object: object)
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that emits elements when broadcasting notifications.
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
#endif
|
||||
}
|
||||
|
||||
extension NotificationCenter {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Foundation are imported.
|
||||
///
|
||||
/// Foundation extends `NotificationCenter` with new methods and nested types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `NotificationCenter.default.publisher(for: name)`,
|
||||
/// because Swift is unable to understand which `publisher` method
|
||||
/// you're referring to — the one declared in Foundation or in OpenCombine.
|
||||
///
|
||||
/// So you have to write `NotificationCenter.default.ocombine.publisher(for: name)`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public var ocombine: OCombine { return .init(self) }
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// Returns a publisher that emits events when broadcasting notifications.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: The name of the notification to publish.
|
||||
/// - object: The object posting the named notfication. If `nil`, the publisher
|
||||
/// emits elements for any object producing a notification with the given name.
|
||||
/// - Returns: A publisher that emits events when broadcasting notifications.
|
||||
public func publisher(for name: Notification.Name,
|
||||
object: AnyObject? = nil) -> OCombine.Publisher {
|
||||
return ocombine.publisher(for: name, object: object)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension NotificationCenter.OCombine.Publisher: Equatable {
|
||||
public static func == (lhs: NotificationCenter.OCombine.Publisher,
|
||||
rhs: NotificationCenter.OCombine.Publisher) -> Bool {
|
||||
return lhs.center == rhs.center &&
|
||||
lhs.name == rhs.name &&
|
||||
lhs.object === rhs.object
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification {
|
||||
fileprivate final class Subscription<Downstream: Subscriber>
|
||||
: OpenCombine.Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Notification, Downstream.Failure == Never
|
||||
{
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
|
||||
private var center: NotificationCenter?
|
||||
|
||||
private let name: Name
|
||||
|
||||
private var object: AnyObject?
|
||||
|
||||
private var observation: AnyObject?
|
||||
|
||||
fileprivate init(center: NotificationCenter,
|
||||
name: Notification.Name,
|
||||
object: AnyObject?,
|
||||
downstream: Downstream) {
|
||||
self.center = center
|
||||
self.name = name
|
||||
self.object = object
|
||||
self.observation = center
|
||||
.addObserver(forName: name, object: object, queue: nil) { [weak self] in
|
||||
self?.didReceiveNotification($0, downstream: downstream)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
private func didReceiveNotification(_ notification: Notification,
|
||||
downstream: Downstream) {
|
||||
lock.lock()
|
||||
guard demand > 0 else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(notification)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard let center = self.center, let observation = self.observation else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.center = nil
|
||||
self.object = nil
|
||||
self.observation = nil
|
||||
lock.unlock()
|
||||
center.removeObserver(observation)
|
||||
}
|
||||
|
||||
fileprivate var description: String { return "NotificationCenter Observer" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("center", center as Any),
|
||||
("name", name),
|
||||
("object", object as Any),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// PropertyListEncoder.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension PropertyListEncoder: TopLevelEncoder {
|
||||
public typealias Output = Data
|
||||
}
|
||||
|
||||
extension PropertyListDecoder: TopLevelDecoder {
|
||||
public typealias Input = Data
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// URLSession.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 13.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
import OpenCombine
|
||||
|
||||
extension URLSession {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Foundation are imported.
|
||||
///
|
||||
/// Foundation extends `URLSession` with new methods and nested types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `URLSession.DataTaskPublisher`,
|
||||
/// because Swift is unable to understand which `DataTaskPublisher`
|
||||
/// you're referring to — the one declared in Foundation or in OpenCombine.
|
||||
///
|
||||
/// So you have to write `URLSession.OCombine.DataTaskPublisher`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine {
|
||||
|
||||
public let session: URLSession
|
||||
|
||||
public init(_ session: URLSession) {
|
||||
self.session = session
|
||||
}
|
||||
|
||||
public struct DataTaskPublisher: Publisher {
|
||||
|
||||
public typealias Output = (data: Data, response: URLResponse)
|
||||
|
||||
public typealias Failure = URLError
|
||||
|
||||
public let request: URLRequest
|
||||
|
||||
public let session: URLSession
|
||||
|
||||
public init(request: URLRequest, session: URLSession) {
|
||||
self.request = request
|
||||
self.session = session
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
let subscription = Inner(parent: self, downstream: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a publisher that wraps a URL session data task for a given URL.
|
||||
///
|
||||
/// The publisher publishes data when the task completes, or terminates if
|
||||
/// the task fails with an error.
|
||||
///
|
||||
/// - Parameter url: The URL for which to create a data task.
|
||||
/// - Returns: A publisher that wraps a data task for the URL.
|
||||
public func dataTaskPublisher(for url: URL) -> DataTaskPublisher {
|
||||
return dataTaskPublisher(for: URLRequest(url: url))
|
||||
}
|
||||
|
||||
/// Returns a publisher that wraps a URL session data task for a given
|
||||
/// URL request.
|
||||
///
|
||||
/// The publisher publishes data when the task completes, or terminates if
|
||||
/// the task fails with an error.
|
||||
///
|
||||
/// - Parameter request: The URL request for which to create a data task.
|
||||
/// - Returns: A publisher that wraps a data task for the URL request.
|
||||
public func dataTaskPublisher(for request: URLRequest) -> DataTaskPublisher {
|
||||
return .init(request: request, session: session)
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
public typealias DataTaskPublisher = OCombine.DataTaskPublisher
|
||||
#endif
|
||||
}
|
||||
|
||||
extension URLSession {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Foundation are imported.
|
||||
///
|
||||
/// Foundation extends `URLSession` with new methods and nested types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `URLSession.shared.dataTaskPublisher(for: url)`,
|
||||
/// because Swift is unable to understand which `dataTaskPublisher` method
|
||||
/// you're referring to — the one declared in Foundation or in OpenCombine.
|
||||
///
|
||||
/// So you have to write `URLSession.shared.ocombine.dataTaskPublisher(for: url)`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public var ocombine: OCombine { return .init(self) }
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// Returns a publisher that wraps a URL session data task for a given URL.
|
||||
///
|
||||
/// The publisher publishes data when the task completes, or terminates if the task
|
||||
/// fails with an error.
|
||||
///
|
||||
/// - Parameter url: The URL for which to create a data task.
|
||||
/// - Returns: A publisher that wraps a data task for the URL.
|
||||
public func dataTaskPublisher(for url: URL) -> DataTaskPublisher {
|
||||
return ocombine.dataTaskPublisher(for: url)
|
||||
}
|
||||
|
||||
/// Returns a publisher that wraps a URL session data task for a given URL request.
|
||||
///
|
||||
/// The publisher publishes data when the task completes, or terminates if the task
|
||||
/// fails with an error.
|
||||
///
|
||||
/// - Parameter request: The URL request for which to create a data task.
|
||||
/// - Returns: A publisher that wraps a data task for the URL request.
|
||||
public func dataTaskPublisher(for request: URLRequest) -> DataTaskPublisher {
|
||||
return ocombine.dataTaskPublisher(for: request)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension URLSession.OCombine.DataTaskPublisher {
|
||||
private class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == (data: Data, response: URLResponse),
|
||||
Downstream.Failure == URLError
|
||||
{
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var parent: URLSession.OCombine.DataTaskPublisher?
|
||||
|
||||
private var downstream: Downstream?
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var task: URLSessionDataTask?
|
||||
|
||||
fileprivate init(parent: URLSession.OCombine.DataTaskPublisher,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let parent = self.parent else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if self.task == nil {
|
||||
task = parent.session.dataTask(with: parent.request,
|
||||
completionHandler: handleResponse)
|
||||
}
|
||||
self.demand += demand
|
||||
let task = self.task
|
||||
lock.unlock()
|
||||
task?.resume()
|
||||
}
|
||||
|
||||
private func handleResponse(data: Data?, response: URLResponse?, error: Error?) {
|
||||
lock.lock()
|
||||
guard demand > 0, parent != nil, let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lockedTerminate()
|
||||
lock.unlock()
|
||||
switch (data, response, error) {
|
||||
case let (data, response?, nil):
|
||||
_ = downstream.receive((data ?? Data(), response))
|
||||
downstream.receive(completion: .finished)
|
||||
case let (_, _, error as URLError):
|
||||
downstream.receive(completion: .failure(error))
|
||||
default:
|
||||
downstream.receive(completion: .failure(URLError(.unknown)))
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard parent != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let task = self.task
|
||||
lockedTerminate()
|
||||
lock.unlock()
|
||||
task?.cancel()
|
||||
}
|
||||
|
||||
private func lockedTerminate() {
|
||||
parent = nil
|
||||
downstream = nil
|
||||
demand = .none
|
||||
task = nil
|
||||
}
|
||||
|
||||
var description: String { return "DataTaskPublisher" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("task", task as Any),
|
||||
("downstream", downstream as Any),
|
||||
("parent", parent as Any),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,34 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
func testSchedulerTimeTypeDistance() {
|
||||
let time1 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let time2 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10431))
|
||||
let distantFuture = Scheduler.SchedulerTimeType(.distantFuture)
|
||||
let notSoDistantFuture = Scheduler.SchedulerTimeType(
|
||||
DispatchTime(
|
||||
uptimeNanoseconds: DispatchTime.distantFuture.uptimeNanoseconds - 1024
|
||||
)
|
||||
)
|
||||
|
||||
XCTAssertEqual(time1.distance(to: time2), .nanoseconds(431))
|
||||
XCTAssertEqual(time2.distance(to: time1), .nanoseconds(-431))
|
||||
|
||||
XCTAssertEqual(time1.distance(to: distantFuture), .nanoseconds(-10001))
|
||||
XCTAssertEqual(distantFuture.distance(to: time1), .nanoseconds(10001))
|
||||
XCTAssertEqual(time2.distance(to: distantFuture), .nanoseconds(-10432))
|
||||
XCTAssertEqual(distantFuture.distance(to: time2), .nanoseconds(10432))
|
||||
|
||||
XCTAssertEqual(time1.distance(to: notSoDistantFuture), .nanoseconds(-11025))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time1), .nanoseconds(11025))
|
||||
XCTAssertEqual(time2.distance(to: notSoDistantFuture), .nanoseconds(-11456))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time2), .nanoseconds(11456))
|
||||
|
||||
XCTAssertEqual(distantFuture.distance(to: distantFuture), .nanoseconds(0))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: notSoDistantFuture),
|
||||
.nanoseconds(0))
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeAdvanced() {
|
||||
let time = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let beginningOfTime = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 1))
|
||||
let stride1 = Scheduler.SchedulerTimeType.Stride.nanoseconds(431)
|
||||
let stride2 = Scheduler.SchedulerTimeType.Stride.nanoseconds(-220)
|
||||
|
||||
@@ -38,6 +59,12 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(time.advanced(by: stride2),
|
||||
Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 9780)))
|
||||
|
||||
XCTAssertEqual(time.advanced(by: .nanoseconds(.max)).dispatchTime,
|
||||
.distantFuture)
|
||||
|
||||
XCTAssertEqual(beginningOfTime.advanced(by: .nanoseconds(-1000)).dispatchTime,
|
||||
DispatchTime(uptimeNanoseconds: 1))
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeEquatable() {
|
||||
@@ -84,41 +111,145 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
// MARK: - Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
func testStrideToDispatchTimeInterval() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
switch (Stride.seconds(2).timeInterval,
|
||||
Stride.milliseconds(2).timeInterval,
|
||||
Stride.microseconds(2).timeInterval,
|
||||
Stride.nanoseconds(2).timeInterval) {
|
||||
Stride.nanoseconds(2).timeInterval,
|
||||
Stride.nanoseconds(.max).timeInterval) {
|
||||
case (.nanoseconds(2_000_000_000),
|
||||
.nanoseconds(2_000_000),
|
||||
.nanoseconds(2_000),
|
||||
.nanoseconds(2)):
|
||||
.nanoseconds(2),
|
||||
.nanoseconds(.max)):
|
||||
break // pass
|
||||
case let intervals:
|
||||
XCTFail("Unexpected DispatchTimeInterval: \(intervals)")
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideFromDispatchTimeInterval() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
func testStrideFromDispatchTimeInterval() throws {
|
||||
XCTAssertEqual(Stride(.seconds(2)).magnitude, 2_000_000_000)
|
||||
XCTAssertEqual(Stride(.milliseconds(2)).magnitude, 2_000_000)
|
||||
XCTAssertEqual(Stride(.microseconds(2)).magnitude, 2_000)
|
||||
XCTAssertEqual(Stride(.nanoseconds(2)).magnitude, 2)
|
||||
|
||||
XCTAssertEqual(Stride(.never).magnitude, .max)
|
||||
XCTAssertEqual(Stride(.nanoseconds(.max)).magnitude, .max)
|
||||
XCTAssertEqual(Stride(.nanoseconds(.min)).magnitude, .min)
|
||||
XCTAssertEqual(Stride(.microseconds(.max)).magnitude, .max)
|
||||
XCTAssertEqual(Stride(.microseconds(.min)).magnitude, .min)
|
||||
XCTAssertEqual(Stride(.milliseconds(.max)).magnitude, .max)
|
||||
XCTAssertEqual(Stride(.milliseconds(.min)).magnitude, .min)
|
||||
XCTAssertEqual(Stride(.seconds(.max)).magnitude, .max)
|
||||
XCTAssertEqual(Stride(.seconds(.min)).magnitude, .min)
|
||||
}
|
||||
|
||||
func testStrideFromUnknownDispatchTimeIntervalCase() {
|
||||
// Here we're testing out internal API that is not present in Combine.
|
||||
// Although we prefer only testing public APIs, this case is special.
|
||||
let makeStride: (DispatchTimeInterval) -> Stride
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
makeStride = Stride.init(_:)
|
||||
#else
|
||||
makeStride = Stride.init(__guessFromUnknown:)
|
||||
#endif
|
||||
|
||||
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
|
||||
// 64-bit platforms
|
||||
let minNanoseconds = -0x13B13B13B13B13B0 // Int64.min / 6.5
|
||||
let maxNanoseconds = 0x2C4EC4EC4EC4EC4D // Int64.max / 2.889
|
||||
#elseif arch(i386) || arch(arm)
|
||||
// 32-bit platforms
|
||||
let minNanoseconds = Int.min + 1
|
||||
let maxNanoseconds = Int.max
|
||||
#else
|
||||
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.")
|
||||
#endif
|
||||
XCTAssertEqual(makeStride(.nanoseconds(minNanoseconds)).magnitude, minNanoseconds)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-128)).magnitude, -128)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-57)).magnitude, -57)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-33)).magnitude, -33)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-17)).magnitude, -17)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-8)).magnitude, -8)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-3)).magnitude, -3)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(-1)).magnitude, -1)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(0)).magnitude, 0)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(1)).magnitude, 1)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(3)).magnitude, 3)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(8)).magnitude, 8)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(17)).magnitude, 17)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(33)).magnitude, 33)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(57)).magnitude, 57)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(128)).magnitude, 128)
|
||||
XCTAssertEqual(makeStride(.nanoseconds(maxNanoseconds)).magnitude, maxNanoseconds)
|
||||
|
||||
XCTAssertEqual(makeStride(.microseconds(-128)).magnitude, -128_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(-57)).magnitude, -57_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(-33)).magnitude, -33_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(-17)).magnitude, -17_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(-8)).magnitude, -8_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(-3)).magnitude, -3_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(-1)).magnitude, -1_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(0)).magnitude, 0)
|
||||
XCTAssertEqual(makeStride(.microseconds(1)).magnitude, 1_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(3)).magnitude, 3_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(8)).magnitude, 8_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(17)).magnitude, 17_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(33)).magnitude, 33_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(57)).magnitude, 57_000)
|
||||
XCTAssertEqual(makeStride(.microseconds(128)).magnitude, 128_000)
|
||||
|
||||
XCTAssertEqual(makeStride(.milliseconds(-128)).magnitude, -128_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(-57)).magnitude, -57_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(-33)).magnitude, -33_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(-17)).magnitude, -17_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(-8)).magnitude, -8_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(-3)).magnitude, -3_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(-1)).magnitude, -1_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(0)).magnitude, 0)
|
||||
XCTAssertEqual(makeStride(.milliseconds(1)).magnitude, 1_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(3)).magnitude, 3_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(8)).magnitude, 8_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(17)).magnitude, 17_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(33)).magnitude, 33_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(57)).magnitude, 57_000_000)
|
||||
XCTAssertEqual(makeStride(.milliseconds(128)).magnitude, 128_000_000)
|
||||
|
||||
XCTAssertEqual(makeStride(.seconds(-2)).magnitude, -2_000_000_000)
|
||||
XCTAssertEqual(makeStride(.seconds(-1)).magnitude, -1_000_000_000)
|
||||
XCTAssertEqual(makeStride(.seconds(0)).magnitude, 0)
|
||||
XCTAssertEqual(makeStride(.seconds(1)).magnitude, 1_000_000_000)
|
||||
XCTAssertEqual(makeStride(.seconds(2)).magnitude, 2_000_000_000)
|
||||
}
|
||||
|
||||
func testStrideFromNumericValue() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual(Stride.seconds(1.2).magnitude, 1_200_000_000)
|
||||
XCTAssertEqual(Stride.seconds(2).magnitude, 2_000_000_000)
|
||||
XCTAssertEqual(Stride.milliseconds(2).magnitude, 2_000_000)
|
||||
XCTAssertEqual(Stride.microseconds(2).magnitude, 2_000)
|
||||
XCTAssertEqual(Stride.nanoseconds(2).magnitude, 2)
|
||||
|
||||
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
|
||||
// 64-bit platforms
|
||||
XCTAssertEqual(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000 - 1).magnitude,
|
||||
9223372035854776320
|
||||
)
|
||||
#elseif arch(i386) || arch(arm)
|
||||
// 32-bit platforms
|
||||
XCTAssertEqual(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000).magnitude,
|
||||
.max
|
||||
)
|
||||
#else
|
||||
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.")
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(Stride.seconds(.max).magnitude, .max)
|
||||
XCTAssertEqual(Stride.milliseconds(.max).magnitude, .max)
|
||||
XCTAssertEqual(Stride.microseconds(.max).magnitude, .max)
|
||||
XCTAssertEqual(Stride.nanoseconds(.max).magnitude, .max)
|
||||
|
||||
XCTAssertEqual((1.2 as Stride).magnitude, 1_200_000_000)
|
||||
XCTAssertEqual((2 as Stride).magnitude, 2_000_000_000)
|
||||
|
||||
@@ -126,17 +257,33 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
XCTAssertEqual(Stride(exactly: 871 as UInt64)?.magnitude, 871)
|
||||
}
|
||||
|
||||
func testStrideComparable() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
func testStrideFromTooMuchSecondsCrashes() {
|
||||
assertCrashes {
|
||||
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
|
||||
// 64-bit platforms
|
||||
XCTAssertGreaterThan(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000).magnitude,
|
||||
.max
|
||||
)
|
||||
#elseif arch(i386) || arch(arm)
|
||||
// 32-bit platforms
|
||||
XCTAssertGreaterThan(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000 + 1).magnitude,
|
||||
.max
|
||||
)
|
||||
#else
|
||||
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideComparable() {
|
||||
XCTAssertLessThan(Stride.nanoseconds(1), .nanoseconds(2))
|
||||
XCTAssertGreaterThan(Stride.nanoseconds(-2), .microseconds(-10))
|
||||
XCTAssertLessThan(Stride.milliseconds(2), .seconds(2))
|
||||
}
|
||||
|
||||
func testStrideMultiplication() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) * .nanoseconds(61346)).magnitude, 0)
|
||||
XCTAssertEqual((Stride.nanoseconds(61346) * .nanoseconds(0)).magnitude, 0)
|
||||
XCTAssertEqual((Stride.nanoseconds(18) * .nanoseconds(1)).magnitude, 18)
|
||||
@@ -196,8 +343,6 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStrideAddition() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) + .microseconds(2)).magnitude, 2000)
|
||||
XCTAssertEqual((Stride.nanoseconds(2) + .microseconds(0)).magnitude, 2)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) + .nanoseconds(12)).magnitude, 19)
|
||||
@@ -243,8 +388,6 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStrideSubtraction() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) - .microseconds(2)).magnitude, -2000)
|
||||
XCTAssertEqual((Stride.nanoseconds(2) - .microseconds(0)).magnitude, 2)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) - .nanoseconds(12)).magnitude, -5)
|
||||
@@ -290,8 +433,6 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStrideCodable() throws {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
@@ -429,6 +570,9 @@ private let backgroundScheduler = DispatchQueue.global(qos: .background).ocombin
|
||||
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
private struct KeyedWrapper<Value: Codable & Equatable>: Codable, Equatable {
|
||||
let value: Value
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// TopLevelDecoder+Extensions.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/29/19.
|
||||
//
|
||||
|
||||
#if !OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension JSONDecoder: TopLevelDecoder {}
|
||||
extension JSONEncoder: TopLevelEncoder {}
|
||||
|
||||
extension PropertyListDecoder: TopLevelDecoder {}
|
||||
extension PropertyListEncoder: TopLevelEncoder {}
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// JSONDecoderTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class JSONDecoderTests: XCTestCase {
|
||||
func testSuccessfullyDecode() {
|
||||
let decoder = JSONDecoder()
|
||||
let input = #"[{"success":true}]"#
|
||||
var actualOutput: [Subscribers.Completion<TestingError>]?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
let cancellable = Just(input)
|
||||
.map { Data($0.utf8) }
|
||||
.decode(type: [Subscribers.Completion<TestingError>].self, decoder: decoder)
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTAssertEqual(actualOutput, [.finished])
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testDecodingFailure() {
|
||||
let decoder = JSONDecoder()
|
||||
let input = #"{"a":1,"b":2}"#
|
||||
var actualOutput: [Int]?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
let cancellable = Just(input)
|
||||
.map { Data($0.utf8) }
|
||||
.decode(type: [Int].self, decoder: decoder)
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTFail("Unexpected success")
|
||||
case .failure(DecodingError.typeMismatch)?:
|
||||
XCTAssertNil(actualOutput)
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// JSONEncoderTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class JSONEncoderTests: XCTestCase {
|
||||
|
||||
func testSuccessfullyEncode() {
|
||||
let encoder = JSONEncoder()
|
||||
let input = [Subscribers.Completion<TestingError>.finished]
|
||||
var actualOutput: String?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
|
||||
let cancellable = Just(input)
|
||||
.encode(encoder: encoder)
|
||||
.map { String(decoding: $0, as: UTF8.self) }
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTAssertEqual(actualOutput, #"[{"success":true}]"#)
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testEncodingFailure() {
|
||||
let encoder = JSONEncoder()
|
||||
let input = Double.nan
|
||||
var actualOutput: String?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
let cancellable = Just(input)
|
||||
.encode(encoder: encoder)
|
||||
.map { String(decoding: $0, as: UTF8.self) }
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTFail("Unexpected success")
|
||||
case .failure(EncodingError.invalidValue)?:
|
||||
XCTAssertNil(actualOutput)
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
//
|
||||
// NotificationCenterTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class NotificationCenterTests: XCTestCase {
|
||||
|
||||
func testRequestingDemand() {
|
||||
|
||||
let initialDemands: [Subscribers.Demand?] = [
|
||||
nil,
|
||||
.max(1),
|
||||
.max(2),
|
||||
.max(10),
|
||||
.unlimited
|
||||
]
|
||||
|
||||
let subsequentDemands: [[Subscribers.Demand]] = [
|
||||
Array(repeating: .max(0), count: 5),
|
||||
Array(repeating: .max(1), count: 10),
|
||||
[.max(1), .max(0), .max(1), .max(0)],
|
||||
[.max(0), .max(1), .max(2)],
|
||||
[.unlimited, .max(1)]
|
||||
]
|
||||
|
||||
var numberOfInputsHistory: [Int] = []
|
||||
let expectedNumberOfInputsHistory = [
|
||||
0, 0, 0, 0, 0, 1, 11, 2, 1, 20, 2, 12, 4, 5, 20, 10, 20, 12, 13, 20, 20,
|
||||
20, 20, 20, 20
|
||||
]
|
||||
|
||||
for initialDemand in initialDemands {
|
||||
for subsequentDemand in subsequentDemands {
|
||||
|
||||
var i = 0
|
||||
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
|
||||
let subscriber = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { initialDemand.map($0.request) },
|
||||
receiveValue: { _ in
|
||||
defer { i += 1 }
|
||||
return i < subsequentDemand.endIndex ? subsequentDemand[i] : .none
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(subscriber.subscriptions.count, 0)
|
||||
XCTAssertEqual(subscriber.inputs.count, 0)
|
||||
XCTAssertEqual(subscriber.completions.count, 0)
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.subscriptions.count, 1)
|
||||
XCTAssertEqual(subscriber.inputs.count, 0)
|
||||
XCTAssertEqual(subscriber.completions.count, 0)
|
||||
|
||||
for _ in 0..<20 {
|
||||
center.post(name: name, object: TestObject.two)
|
||||
}
|
||||
|
||||
XCTAssertEqual(subscriber.subscriptions.count, 1)
|
||||
XCTAssertEqual(subscriber.completions.count, 0)
|
||||
|
||||
numberOfInputsHistory.append(subscriber.inputs.count)
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
|
||||
}
|
||||
|
||||
func testBasicBehavior() throws {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: TestObject.one)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
|
||||
XCTAssertEqual(center.history, [])
|
||||
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, TestObject.one, nil)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer")])
|
||||
|
||||
let note = Notification(name: name, object: TestObject.one, userInfo: nil)
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, TestObject.one, nil),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer")])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, TestObject.one, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer"),
|
||||
.value(note)])
|
||||
|
||||
let unrelatedNote1 = Notification(name: Notification.Name("unrelatedNote1"),
|
||||
object: TestObject.one,
|
||||
userInfo: nil)
|
||||
center.post(unrelatedNote1)
|
||||
|
||||
let unrelatedNote2 = Notification(name: name,
|
||||
object: TestObject.two,
|
||||
userInfo: nil)
|
||||
center.post(unrelatedNote2)
|
||||
center.post(name: name, object: nil)
|
||||
center.post(name: name, object: nil)
|
||||
center.post(name: name, object: TestObject.one)
|
||||
center.post(name: name, object: TestObject.one)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, TestObject.one, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note),
|
||||
.postNotification(unrelatedNote1),
|
||||
.postNotification(unrelatedNote2),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(Notification(name: name,
|
||||
object: TestObject.one)),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(Notification(name: name,
|
||||
object: TestObject.one))])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer"),
|
||||
.value(note),
|
||||
.value(unrelatedNote1),
|
||||
.value(unrelatedNote2)])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.unlimited)
|
||||
|
||||
center.post(note)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, TestObject.one, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note),
|
||||
.postNotification(unrelatedNote1),
|
||||
.postNotification(unrelatedNote2),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(Notification(name: name,
|
||||
object: TestObject.one)),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(Notification(name: name,
|
||||
object: TestObject.one)),
|
||||
.postNotification(note),
|
||||
.removeObserver,
|
||||
.removeObserverForName(nil, nil),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer"),
|
||||
.value(note),
|
||||
.value(unrelatedNote1),
|
||||
.value(unrelatedNote2),
|
||||
.value(note)])
|
||||
}
|
||||
|
||||
func testBasicBehaviorNilObject() throws {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
|
||||
XCTAssertEqual(center.history, [])
|
||||
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer")])
|
||||
|
||||
let note = Notification(name: name, object: TestObject.one, userInfo: nil)
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer")])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer"),
|
||||
.value(note)])
|
||||
|
||||
let unrelatedNote = Notification(name: Notification.Name("unrelatedNote"),
|
||||
object: TestObject.one,
|
||||
userInfo: nil)
|
||||
center.post(unrelatedNote)
|
||||
center.post(name: name, object: nil)
|
||||
center.post(name: name, object: nil)
|
||||
center.post(name: name, object: TestObject.one)
|
||||
center.post(name: name, object: TestObject.one)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note),
|
||||
.postNotification(unrelatedNote),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(note),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer"),
|
||||
.value(note),
|
||||
.value(unrelatedNote),
|
||||
.value(Notification(name: name))])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.unlimited)
|
||||
|
||||
center.post(note)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note),
|
||||
.postNotification(unrelatedNote),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name)),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(note),
|
||||
.postNotificationWithName(name, TestObject.one, nil),
|
||||
.postNotification(note),
|
||||
.postNotification(note),
|
||||
.removeObserver,
|
||||
.removeObserverForName(nil, nil),
|
||||
.postNotification(note)])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("NotificationCenter Observer"),
|
||||
.value(note),
|
||||
.value(unrelatedNote),
|
||||
.value(Notification(name: name)),
|
||||
.value(note)])
|
||||
}
|
||||
|
||||
func testRecursivelyReceiveValue() throws {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { $0.request(.max(3)) },
|
||||
receiveValue: { _ in .unlimited }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
let note = Notification(name: name)
|
||||
var recursionCounter = 7
|
||||
tracking.onValue = { _ in
|
||||
if recursionCounter == 0 { return }
|
||||
recursionCounter -= 1
|
||||
center.post(note)
|
||||
}
|
||||
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("NotificationCenter Observer"),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note)])
|
||||
|
||||
center.post(note)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("NotificationCenter Observer"),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note),
|
||||
.value(note)])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("NotificationCenter Observer")])
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil),
|
||||
.removeObserver,
|
||||
.removeObserverForName(nil, nil)])
|
||||
}
|
||||
|
||||
func testCancellingReleasesNotificationCenter() throws {
|
||||
var centerDestroyed = false
|
||||
var downstreamSubscription: Subscription?
|
||||
do {
|
||||
let center = TestNotificationCenter()
|
||||
center.onDeinit = { centerDestroyed = true }
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
}
|
||||
XCTAssertFalse(centerDestroyed)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
XCTAssertTrue(centerDestroyed)
|
||||
}
|
||||
|
||||
func testWeakCaptureWhenAddingObserver() {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name("testName")
|
||||
var value: Notification?
|
||||
do {
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { $0.request(.max(1)) },
|
||||
receiveValue: { value = $0; return .none }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
tracking.clearHistory() // Release the subscription
|
||||
}
|
||||
center.post(name: name, object: nil)
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil),
|
||||
.postNotificationWithName(name, nil, nil),
|
||||
.postNotification(Notification(name: name))])
|
||||
XCTAssertNil(value)
|
||||
}
|
||||
|
||||
func testZeroDemand() throws {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let publisher = makePublisher(center, for: name, object: nil)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Notification, Never>(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).request(.none)
|
||||
|
||||
XCTAssertEqual(center.history,
|
||||
[.addObserver(name, nil, nil)])
|
||||
XCTAssertEqual(tracking.history, [.subscription("NotificationCenter Observer")])
|
||||
}
|
||||
|
||||
func testNotificationCenterSubscriptionReflection() throws {
|
||||
let center = TestNotificationCenter()
|
||||
let name = Notification.Name(rawValue: "testName")
|
||||
let object = TestObject.one
|
||||
let publisher = makePublisher(center, for: name, object: object)
|
||||
|
||||
try testSubscriptionReflection(
|
||||
description: "NotificationCenter Observer",
|
||||
customMirror: expectedChildren(
|
||||
("center", .matches(String(describing: Optional(center)))),
|
||||
("name", .contains(String(describing: name))),
|
||||
("object", .matches(String(describing: Optional(object)))),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
playgroundDescription: "NotificationCenter Observer",
|
||||
sut: publisher
|
||||
)
|
||||
}
|
||||
|
||||
func testEquatable() {
|
||||
let center1 = NotificationCenter()
|
||||
let center2 = NotificationCenter()
|
||||
let name1 = Notification.Name(rawValue: "abcdefg")
|
||||
let name2 = Notification.Name(rawValue: "1234567")
|
||||
let object1 = TestObject.one
|
||||
let object2 = TestObject.two
|
||||
|
||||
XCTAssertEqual(makePublisher(center1, for: name1, object: object1),
|
||||
makePublisher(center1, for: name1, object: object1))
|
||||
XCTAssertEqual(makePublisher(center2, for: name2, object: object2),
|
||||
makePublisher(center2, for: name2, object: object2))
|
||||
XCTAssertEqual(makePublisher(center1, for: name1, object: nil),
|
||||
makePublisher(center1, for: name1, object: nil))
|
||||
XCTAssertNotEqual(makePublisher(center1, for: name1, object: object1),
|
||||
makePublisher(center1, for: name1, object: nil))
|
||||
XCTAssertNotEqual(makePublisher(center1, for: name1, object: nil),
|
||||
makePublisher(center1, for: name1, object: object2))
|
||||
XCTAssertNotEqual(makePublisher(center1, for: name1, object: object1),
|
||||
makePublisher(center1, for: name1, object: object2))
|
||||
XCTAssertNotEqual(makePublisher(center1, for: name1, object: object1),
|
||||
makePublisher(center1, for: name2, object: object1))
|
||||
XCTAssertNotEqual(makePublisher(center1, for: name1, object: object1),
|
||||
makePublisher(center2, for: name1, object: object1))
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func makePublisher(
|
||||
_ center: NotificationCenter,
|
||||
for name: Notification.Name,
|
||||
object: AnyObject?
|
||||
) -> NotificationCenter.Publisher {
|
||||
return center.publisher(for: name, object: object)
|
||||
}
|
||||
#else
|
||||
private func makePublisher(
|
||||
_ center: NotificationCenter,
|
||||
for name: Notification.Name,
|
||||
object: AnyObject?
|
||||
) -> NotificationCenter.OCombine.Publisher {
|
||||
return center.ocombine.publisher(for: name, object: object)
|
||||
}
|
||||
#endif
|
||||
|
||||
/// A simple mock notification center that always sends notifications to **all**
|
||||
/// observers in non-thread safe manner.
|
||||
private final class TestNotificationCenter: NotificationCenter {
|
||||
|
||||
enum Event {
|
||||
case postNotificationWithName(Notification.Name, Any?, [AnyHashable : Any]?)
|
||||
case postNotification(Notification)
|
||||
case addObserver(Notification.Name?, Any?, OperationQueue?)
|
||||
case removeObserver
|
||||
case removeObserverForName(Notification.Name?, Any?)
|
||||
}
|
||||
|
||||
private final class Observation {
|
||||
let callback: (Notification) -> Void
|
||||
|
||||
init(callback: @escaping (Notification) -> Void) {
|
||||
self.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
private final class Token: NSObject {
|
||||
weak var observation: Observation?
|
||||
|
||||
init(observer: TestNotificationCenter.Observation) {
|
||||
self.observation = observer
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var history = [Event]()
|
||||
|
||||
private var observations: [Observation] = []
|
||||
|
||||
var onDeinit: (() -> Void)?
|
||||
|
||||
deinit {
|
||||
onDeinit?()
|
||||
}
|
||||
|
||||
override func post(name aName: Notification.Name,
|
||||
object anObject: Any?,
|
||||
userInfo aUserInfo: [AnyHashable : Any]? = nil) {
|
||||
history.append(.postNotificationWithName(aName, anObject, aUserInfo))
|
||||
let notification = Notification(name: aName,
|
||||
object: anObject,
|
||||
userInfo: aUserInfo)
|
||||
post(notification)
|
||||
}
|
||||
|
||||
override func post(_ notification: Notification) {
|
||||
history.append(.postNotification(notification))
|
||||
for observation in observations {
|
||||
observation.callback(notification)
|
||||
}
|
||||
}
|
||||
|
||||
override func addObserver(
|
||||
forName name: NSNotification.Name?,
|
||||
object obj: Any?,
|
||||
queue: OperationQueue?,
|
||||
using block: @escaping (Notification) -> Void
|
||||
) -> NSObjectProtocol {
|
||||
history.append(.addObserver(name, obj, queue))
|
||||
let observer = Observation(callback: block)
|
||||
observations.append(observer)
|
||||
return Token(observer: observer)
|
||||
}
|
||||
|
||||
override func removeObserver(_ observer: Any) {
|
||||
history.append(.removeObserver)
|
||||
removeObserver(observer, name: nil, object: nil)
|
||||
}
|
||||
|
||||
override func removeObserver(_ observer: Any,
|
||||
name aName: NSNotification.Name?,
|
||||
object anObject: Any?) {
|
||||
history.append(.removeObserverForName(aName, anObject))
|
||||
guard let observer = observer as? Token else { return }
|
||||
observations.removeAll { $0 === observer.observation }
|
||||
}
|
||||
}
|
||||
|
||||
private final class TestObject: NSObject {
|
||||
|
||||
static let one = TestObject()
|
||||
|
||||
static let two = TestObject()
|
||||
}
|
||||
|
||||
extension TestNotificationCenter.Event: Equatable {
|
||||
fileprivate static func == (lhs: TestNotificationCenter.Event,
|
||||
rhs: TestNotificationCenter.Event) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.postNotification(lhsNote), .postNotification(rhsNote)):
|
||||
return lhsNote == rhsNote
|
||||
case let (.postNotificationWithName(lhsName,
|
||||
lhsObject as TestObject?,
|
||||
lhsUserInfo),
|
||||
.postNotificationWithName(rhsName,
|
||||
rhsObject as TestObject?,
|
||||
rhsUserInfo)):
|
||||
return lhsName == rhsName &&
|
||||
lhsObject === rhsObject &&
|
||||
(lhsUserInfo == nil) == (rhsUserInfo == nil)
|
||||
case let (.addObserver(lhsName, lhsObject as TestObject?, lhsQueue),
|
||||
.addObserver(rhsName, rhsObject as TestObject?, rhsQueue)):
|
||||
return lhsName == rhsName &&
|
||||
lhsObject === rhsObject &&
|
||||
lhsQueue == rhsQueue
|
||||
case (.removeObserver, .removeObserver):
|
||||
return true
|
||||
case let (.removeObserverForName(lhsName, lhsObject as TestObject?),
|
||||
.removeObserverForName(rhsName, rhsObject as TestObject?)):
|
||||
return lhsName == rhsName && lhsObject === rhsObject
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TestNotificationCenter.Event: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .postNotificationWithName(name, object, userInfo):
|
||||
return """
|
||||
.postNotificationWithName(\
|
||||
.init(rawValue: \"\(name.rawValue)\"), \
|
||||
\(object.map(String.init(describing:)) ?? "nil"), \
|
||||
\(userInfo.map(String.init(describing:)) ?? "nil"))
|
||||
"""
|
||||
case .postNotification:
|
||||
return ".postNotification(note)"
|
||||
case let .addObserver(name, object, queue):
|
||||
let nameDescription = name.map { ".init(rawValue: \($0.rawValue))" } ?? "nil"
|
||||
return """
|
||||
.addObserver(\
|
||||
\(nameDescription), \
|
||||
\(object.map(String.init(describing:)) ?? "nil"), \
|
||||
\(queue.map(String.init(describing:)) ?? "nil"))
|
||||
"""
|
||||
case .removeObserver:
|
||||
return ".removeObserver"
|
||||
case let .removeObserverForName(name, object):
|
||||
let nameDescription = name.map { ".init(rawValue: \($0.rawValue))" } ?? "nil"
|
||||
return """
|
||||
.removeObserverForName(\
|
||||
\(nameDescription), \
|
||||
\(object.map(String.init(describing:)) ?? "nil"))
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// PropertyListDecoderTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PropertyListDecoderTests: XCTestCase {
|
||||
func testSuccessfullyDecode() {
|
||||
let decoder = PropertyListDecoder()
|
||||
let input = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<array>
|
||||
\t<dict>
|
||||
\t\t<key>success</key>
|
||||
\t\t<true/>
|
||||
\t</dict>
|
||||
</array>
|
||||
</plist>
|
||||
|
||||
"""
|
||||
var actualOutput: [Subscribers.Completion<TestingError>]?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
let cancellable = Just(input)
|
||||
.map { Data($0.utf8) }
|
||||
.decode(type: [Subscribers.Completion<TestingError>].self, decoder: decoder)
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTAssertEqual(actualOutput, [.finished])
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testDecodingFailure() {
|
||||
let decoder = PropertyListDecoder()
|
||||
let input = "000000"
|
||||
var actualOutput: [Int]?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
let cancellable = Just(input)
|
||||
.map { Data($0.utf8) }
|
||||
.decode(type: [Int].self, decoder: decoder)
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTFail("Unexpected success")
|
||||
case .failure(DecodingError.typeMismatch)?:
|
||||
XCTAssertNil(actualOutput)
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// PropertyListEncoderTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PropertyListEncoderTests: XCTestCase {
|
||||
|
||||
func testSuccessfullyEncode() {
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .xml
|
||||
let input = [Subscribers.Completion<TestingError>.finished]
|
||||
var actualOutput: String?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
|
||||
let cancellable = Just(input)
|
||||
.encode(encoder: encoder)
|
||||
.map { String(decoding: $0, as: UTF8.self) }
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTAssertEqual(actualOutput, """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<array>
|
||||
\t<dict>
|
||||
\t\t<key>success</key>
|
||||
\t\t<true/>
|
||||
\t</dict>
|
||||
</array>
|
||||
</plist>
|
||||
|
||||
""")
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testEncodingFailure() {
|
||||
let encoder = PropertyListEncoder()
|
||||
let input = Double.nan
|
||||
var actualOutput: String?
|
||||
var actualCompletion: Subscribers.Completion<Error>?
|
||||
let cancellable = Just(input)
|
||||
.encode(encoder: encoder)
|
||||
.map { String(decoding: $0, as: UTF8.self) }
|
||||
.sink(receiveCompletion: { actualCompletion = $0 },
|
||||
receiveValue: { actualOutput = $0 })
|
||||
|
||||
switch actualCompletion {
|
||||
case .finished?:
|
||||
XCTFail("Unexpected success")
|
||||
case .failure(EncodingError.invalidValue)?:
|
||||
XCTAssertNil(actualOutput)
|
||||
case .failure(let error)?:
|
||||
XCTFail("Unexpected failure received: \(error)")
|
||||
case nil:
|
||||
XCTFail("Expected completion")
|
||||
}
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,724 @@
|
||||
//
|
||||
// URLSessionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 13.12.2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_arguments
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
// We can't test it on non-Darwin platforms because swift-corelibs-foundation
|
||||
// doesn't allow us to override some URLSession methods that we need.
|
||||
//
|
||||
// As soon as https://github.com/apple/swift-corelibs-foundation/pull/2587 makes it
|
||||
// into a release, we can enable these tests on non-Darwin platforms.
|
||||
//
|
||||
// The publisher itself though should work alright on those platforms.
|
||||
#if canImport(Darwin)
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class URLSessionTests: XCTestCase {
|
||||
|
||||
private typealias TrackingSubscriber =
|
||||
TrackingSubscriberBase<(data: Data, response: URLResponse), URLError>
|
||||
|
||||
private let testURL = URL(string: "https://github.com")!
|
||||
|
||||
private let testRequest = URLRequest(url: URL(string: "https://github.com")!,
|
||||
cachePolicy: .reloadIgnoringCacheData,
|
||||
timeoutInterval: 42)
|
||||
|
||||
private let testData = Data("test data".utf8)
|
||||
|
||||
private let testResponse = URLResponse(url: URL(string: "https://example.com")!,
|
||||
mimeType: "text/markdown",
|
||||
expectedContentLength: 300,
|
||||
textEncodingName: "utf-8")
|
||||
|
||||
private let testError = URLError(.cannotParseResponse, userInfo: ["a" : 1])
|
||||
|
||||
private let unknownError = URLError(.unknown)
|
||||
|
||||
func testDataTaskPublisherFromURL() {
|
||||
let publisher = makePublisher(TestURLSession(testDataTask: .init()), testURL)
|
||||
let expectedRequest = URLRequest(url: testURL)
|
||||
XCTAssertEqual(publisher.request, expectedRequest)
|
||||
}
|
||||
|
||||
func testDataTaskPublisherFromRequest() {
|
||||
let publisher = makePublisher(TestURLSession(testDataTask: .init()), testRequest)
|
||||
XCTAssertEqual(publisher.request, testRequest)
|
||||
}
|
||||
|
||||
func testReceiveNothing() {
|
||||
testReceiveResult(nil, nil, nil,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(unknownError))])
|
||||
}
|
||||
|
||||
func testReceiveOnlyData() {
|
||||
testReceiveResult(testData, nil, nil,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(unknownError))])
|
||||
}
|
||||
|
||||
func testReceiveDataAndResponse() {
|
||||
testReceiveResult(testData, testResponse, nil,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.value((testData, testResponse)),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testReceiveDataAndURLError() {
|
||||
testReceiveResult(testData, nil, testError,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(testError))])
|
||||
}
|
||||
|
||||
func testReceiveDataAndUnrelatedError() {
|
||||
testReceiveResult(testData, nil, TestingError.oops,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(unknownError))])
|
||||
}
|
||||
|
||||
func testReceiveOnlyResponse() {
|
||||
testReceiveResult(nil, testResponse, nil,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.value((Data(), testResponse)),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testReceiveResponseAndURLError() {
|
||||
testReceiveResult(nil, testResponse, testError,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(testError))])
|
||||
}
|
||||
|
||||
func testReceiveResponseAndUnrelatedError() {
|
||||
testReceiveResult(nil, testResponse, TestingError.oops,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(unknownError))])
|
||||
}
|
||||
|
||||
func testReceiveOnlyURLError() {
|
||||
testReceiveResult(nil, nil, testError,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(testError))])
|
||||
}
|
||||
|
||||
func testReceiveOnlyUnrelatedError() {
|
||||
testReceiveResult(nil, nil, TestingError.oops,
|
||||
expected: [.subscription("DataTaskPublisher"),
|
||||
.completion(.failure(unknownError))])
|
||||
}
|
||||
|
||||
func testRequesting() throws {
|
||||
let dataTask = TestURLSessionDataTask()
|
||||
let session = TestURLSession(testDataTask: dataTask)
|
||||
let publisher = makePublisher(session, testRequest)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [])
|
||||
XCTAssertEqual(session.history, [])
|
||||
|
||||
session.completeDataTasks(testData, testResponse, nil)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(2))
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(1))
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [.resume, .resume])
|
||||
XCTAssertEqual(session.history, [.dataTaskWithRequestAndCompletion(testRequest)])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [.resume, .resume, .cancel])
|
||||
XCTAssertEqual(session.history, [.dataTaskWithRequestAndCompletion(testRequest)])
|
||||
|
||||
session.completeDataTasks(testData, testResponse, nil)
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [.resume, .resume, .cancel])
|
||||
XCTAssertEqual(session.history, [.dataTaskWithRequestAndCompletion(testRequest)])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
let dataTask = TestURLSessionDataTask()
|
||||
let session = TestURLSession(testDataTask: dataTask)
|
||||
let publisher = makePublisher(session, testRequest)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [])
|
||||
XCTAssertEqual(session.history, [])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(1))
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(1))
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [.resume, .cancel])
|
||||
XCTAssertEqual(session.history, [.dataTaskWithRequestAndCompletion(testRequest)])
|
||||
}
|
||||
|
||||
func testCrashesOnZeroDemand() throws {
|
||||
let dataTask = TestURLSessionDataTask()
|
||||
let session = TestURLSession(testDataTask: dataTask)
|
||||
let publisher = makePublisher(session, testURL)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
try assertCrashes {
|
||||
try XCTUnwrap(downstreamSubscription).request(.none)
|
||||
}
|
||||
}
|
||||
|
||||
func testURLSessionSubscriptionReflection() throws {
|
||||
let dataTask = TestURLSessionDataTask()
|
||||
let session = TestURLSession(testDataTask: dataTask)
|
||||
let publisher = makePublisher(session, testURL)
|
||||
try testSubscriptionReflection(
|
||||
description: "DataTaskPublisher",
|
||||
customMirror: expectedChildren(
|
||||
("task", "nil"),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("parent", .matches(String(describing: Optional(publisher)))),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
playgroundDescription: "DataTaskPublisher",
|
||||
sut: publisher
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Generic tests
|
||||
|
||||
private func testReceiveResult(_ data: Data?,
|
||||
_ response: URLResponse?,
|
||||
_ error: Error?,
|
||||
expected: [TrackingSubscriber.Event]) {
|
||||
let dataTask = TestURLSessionDataTask()
|
||||
let session = TestURLSession(testDataTask: dataTask)
|
||||
let publisher = makePublisher(session, testRequest)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
tracking.assertHistoryEqual([.subscription("DataTaskPublisher")],
|
||||
valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [.resume])
|
||||
XCTAssertEqual(session.history, [.dataTaskWithRequestAndCompletion(testRequest)])
|
||||
|
||||
session.completeDataTasks(data, response, error)
|
||||
session.completeDataTasks(data, response, error)
|
||||
session.completeDataTasks(data, response, error)
|
||||
|
||||
tracking.assertHistoryEqual(expected, valueComparator: ==)
|
||||
XCTAssertEqual(dataTask.history, [.resume])
|
||||
XCTAssertEqual(session.history, [.dataTaskWithRequestAndCompletion(testRequest)])
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple mock URLSession that records its history and allows executing
|
||||
/// callbacks synchronously
|
||||
private class TestURLSession: URLSession {
|
||||
|
||||
enum Event: Equatable {
|
||||
case delegateQueue
|
||||
case delegate
|
||||
case configuration
|
||||
case getSessionDescription
|
||||
case setSessionDescription(String?)
|
||||
case finishTasksAndInvalidate
|
||||
case invalidateAndCancel
|
||||
case reset
|
||||
case flush
|
||||
case getTasksWithCompletionHandler
|
||||
case getAllTasks
|
||||
case dataTaskWithRequest(URLRequest)
|
||||
case dataTaskWithRequestAndCompletion(URLRequest)
|
||||
case dataTaskWithURL(URL)
|
||||
case dataTaskWithURLAndCompletion(URL)
|
||||
case uploadTaskWithRequestFromFile(URLRequest, URL)
|
||||
case uploadTaskWithRequestFromFileWithCompletion(URLRequest, URL)
|
||||
case uploadTaskWithRequestFromData(URLRequest, Data)
|
||||
case uploadTaskWithRequestFromDataWithCompletion(URLRequest, Data?)
|
||||
case uploadTaskWithStreamedRequest(URLRequest)
|
||||
case downloadTaskWithRequest(URLRequest)
|
||||
case downloadTaskWithRequestAndCompletion(URLRequest)
|
||||
case downloadTaskWithURL(URL)
|
||||
case downloadTaskWithURLAndCompletion(URL)
|
||||
case downloadTaskWithResumeData(Data)
|
||||
case downloadTaskWithResumeDataAndCompletion(Data)
|
||||
case streamTaskWithHostNameAndPort(String, Int)
|
||||
#if canImport(Darwin) && swift(>=5.1)
|
||||
case streamTaskWithService(NetService)
|
||||
case webSocketTaskWithURL(URL)
|
||||
case webSocketTaskWithURLAndProtocols(URL, [String])
|
||||
case webSocketTaskWithRequest(URLRequest)
|
||||
#endif // canImport(Darwin) && swift(>=5.1)
|
||||
}
|
||||
|
||||
private(set) var history = [Event]()
|
||||
|
||||
private(set) var dataTaskCompletionHandlers: [(Data?, URLResponse?, Error?) -> Void]
|
||||
|
||||
private let testDataTask: TestURLSessionDataTask
|
||||
|
||||
init(testDataTask: TestURLSessionDataTask) {
|
||||
self.testDataTask = testDataTask
|
||||
self.dataTaskCompletionHandlers = []
|
||||
}
|
||||
|
||||
// MARK: Testing
|
||||
|
||||
func completeDataTasks(_ data: Data?, _ response: URLResponse?, _ error: Error?) {
|
||||
for completionHandler in dataTaskCompletionHandlers {
|
||||
completionHandler(data, response, error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Overrides
|
||||
|
||||
override class var shared: URLSession { fatalError("shared session is unavailable") }
|
||||
|
||||
override var delegateQueue: OperationQueue {
|
||||
history.append(.delegateQueue)
|
||||
return super.delegateQueue
|
||||
}
|
||||
|
||||
override var delegate: URLSessionDelegate? {
|
||||
history.append(.delegate)
|
||||
return super.delegate
|
||||
}
|
||||
|
||||
override var configuration: URLSessionConfiguration {
|
||||
history.append(.configuration)
|
||||
return super.configuration
|
||||
}
|
||||
|
||||
override var sessionDescription: String? {
|
||||
get {
|
||||
history.append(.getSessionDescription)
|
||||
return super.sessionDescription
|
||||
}
|
||||
set {
|
||||
history.append(.setSessionDescription(newValue))
|
||||
super.sessionDescription = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override func finishTasksAndInvalidate() {
|
||||
history.append(.finishTasksAndInvalidate)
|
||||
super.finishTasksAndInvalidate()
|
||||
}
|
||||
|
||||
override func invalidateAndCancel() {
|
||||
history.append(.invalidateAndCancel)
|
||||
super.invalidateAndCancel()
|
||||
}
|
||||
|
||||
override func reset(completionHandler: @escaping () -> Void) {
|
||||
history.append(.reset)
|
||||
super.reset(completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func flush(completionHandler: @escaping () -> Void) {
|
||||
history.append(.flush)
|
||||
super.flush(completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func getTasksWithCompletionHandler(
|
||||
_ completionHandler: @escaping ([URLSessionDataTask],
|
||||
[URLSessionUploadTask],
|
||||
[URLSessionDownloadTask]) -> Void
|
||||
) {
|
||||
history.append(.getTasksWithCompletionHandler)
|
||||
super.getTasksWithCompletionHandler(completionHandler)
|
||||
}
|
||||
|
||||
@available(macOS 10.11, iOS 9.0, *)
|
||||
override func getAllTasks(completionHandler: @escaping ([URLSessionTask]) -> Void) {
|
||||
history.append(.getAllTasks)
|
||||
super.getAllTasks(completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func dataTask(with request: URLRequest) -> URLSessionDataTask {
|
||||
history.append(.dataTaskWithRequest(request))
|
||||
return testDataTask
|
||||
}
|
||||
|
||||
override func dataTask(with url: URL) -> URLSessionDataTask {
|
||||
history.append(.dataTaskWithURL(url))
|
||||
return testDataTask
|
||||
}
|
||||
|
||||
override func dataTask(
|
||||
with url: URL,
|
||||
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionDataTask {
|
||||
history.append(.dataTaskWithURLAndCompletion(url))
|
||||
dataTaskCompletionHandlers.append(completionHandler)
|
||||
return testDataTask
|
||||
}
|
||||
|
||||
override func dataTask(
|
||||
with request: URLRequest,
|
||||
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionDataTask {
|
||||
history.append(.dataTaskWithRequestAndCompletion(request))
|
||||
dataTaskCompletionHandlers.append(completionHandler)
|
||||
return testDataTask
|
||||
}
|
||||
|
||||
override func uploadTask(with request: URLRequest,
|
||||
fromFile fileURL: URL) -> URLSessionUploadTask {
|
||||
history.append(.uploadTaskWithRequestFromFile(request, fileURL))
|
||||
return super.uploadTask(with: request, fromFile: fileURL)
|
||||
}
|
||||
|
||||
override func uploadTask(with request: URLRequest,
|
||||
from bodyData: Data) -> URLSessionUploadTask {
|
||||
history.append(.uploadTaskWithRequestFromData(request, bodyData))
|
||||
return super.uploadTask(with: request, from: bodyData)
|
||||
}
|
||||
|
||||
override func uploadTask(
|
||||
withStreamedRequest request: URLRequest
|
||||
) -> URLSessionUploadTask {
|
||||
history.append(.uploadTaskWithStreamedRequest(request))
|
||||
return super.uploadTask(withStreamedRequest: request)
|
||||
}
|
||||
|
||||
override func uploadTask(
|
||||
with request: URLRequest,
|
||||
fromFile fileURL: URL,
|
||||
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionUploadTask {
|
||||
history.append(.uploadTaskWithRequestFromFileWithCompletion(request, fileURL))
|
||||
return super.uploadTask(with: request,
|
||||
fromFile: fileURL,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func uploadTask(
|
||||
with request: URLRequest,
|
||||
from bodyData: Data?,
|
||||
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionUploadTask {
|
||||
history.append(.uploadTaskWithRequestFromDataWithCompletion(request, bodyData))
|
||||
return super.uploadTask(with: request,
|
||||
from: bodyData,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func downloadTask(with request: URLRequest) -> URLSessionDownloadTask {
|
||||
history.append(.downloadTaskWithRequest(request))
|
||||
return super.downloadTask(with: request)
|
||||
}
|
||||
|
||||
override func downloadTask(with url: URL) -> URLSessionDownloadTask {
|
||||
history.append(.downloadTaskWithURL(url))
|
||||
return super.downloadTask(with: url)
|
||||
}
|
||||
|
||||
override func downloadTask(
|
||||
with request: URLRequest,
|
||||
completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionDownloadTask {
|
||||
history.append(.downloadTaskWithRequestAndCompletion(request))
|
||||
return super.downloadTask(with: request, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func downloadTask(
|
||||
with url: URL,
|
||||
completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionDownloadTask {
|
||||
history.append(.downloadTaskWithURLAndCompletion(url))
|
||||
return super.downloadTask(with: url, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func downloadTask(
|
||||
withResumeData resumeData: Data,
|
||||
completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void
|
||||
) -> URLSessionDownloadTask {
|
||||
history.append(.downloadTaskWithResumeDataAndCompletion(resumeData))
|
||||
return super.downloadTask(withResumeData: resumeData,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func downloadTask(
|
||||
withResumeData resumeData: Data
|
||||
) -> URLSessionDownloadTask {
|
||||
history.append(.downloadTaskWithResumeData(resumeData))
|
||||
return super.downloadTask(withResumeData: resumeData)
|
||||
}
|
||||
|
||||
@available(macOS 10.11, iOS 9.0, *)
|
||||
override func streamTask(withHostName hostname: String,
|
||||
port: Int) -> URLSessionStreamTask {
|
||||
history.append(.streamTaskWithHostNameAndPort(hostname, port))
|
||||
return super.streamTask(withHostName: hostname, port: port)
|
||||
}
|
||||
|
||||
#if canImport(Darwin) && swift(>=5.1)
|
||||
@available(macOS 10.11, iOS 9.0, *)
|
||||
override func streamTask(with service: NetService) -> URLSessionStreamTask {
|
||||
history.append(.streamTaskWithService(service))
|
||||
return super.streamTask(with: service)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
override func webSocketTask(with url: URL) -> URLSessionWebSocketTask {
|
||||
history.append(.webSocketTaskWithURL(url))
|
||||
return super.webSocketTask(with: url)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
override func webSocketTask(with url: URL,
|
||||
protocols: [String]) -> URLSessionWebSocketTask {
|
||||
history.append(.webSocketTaskWithURLAndProtocols(url, protocols))
|
||||
return super.webSocketTask(with: url, protocols: protocols)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
override func webSocketTask(with request: URLRequest) -> URLSessionWebSocketTask {
|
||||
history.append(.webSocketTaskWithRequest(request))
|
||||
return super.webSocketTask(with: request)
|
||||
}
|
||||
#endif // canImport(Darwin) && swift(>=5.1)
|
||||
}
|
||||
|
||||
private final class TestURLSessionDataTask: URLSessionDataTask {
|
||||
|
||||
enum Event: Equatable {
|
||||
case taskIdentifier
|
||||
case originalRequest
|
||||
case currentRequest
|
||||
case response
|
||||
case progress
|
||||
case getEarliestBeginDate
|
||||
case setEarliestBeginDate(Date?)
|
||||
case getCountOfBytesClientExpectsToSend
|
||||
case setCountOfBytesClientExpectsToSend(Int64)
|
||||
case getCountOfBytesClientExpectsToReceive
|
||||
case setCountOfBytesClientExpectsToReceive(Int64)
|
||||
case countOfBytesReceived
|
||||
case countOfBytesSent
|
||||
case countOfBytesExpectedToSend
|
||||
case countOfBytesExpectedToReceive
|
||||
case getTaskDescription
|
||||
case setTaskDescription(String?)
|
||||
case cancel
|
||||
case state
|
||||
case error
|
||||
case suspend
|
||||
case resume
|
||||
case getPriority
|
||||
case setPriority(Float)
|
||||
}
|
||||
|
||||
private(set) var history = [Event]()
|
||||
|
||||
override init() {}
|
||||
|
||||
override var taskIdentifier: Int {
|
||||
history.append(.taskIdentifier)
|
||||
return super.taskIdentifier
|
||||
}
|
||||
|
||||
override var originalRequest: URLRequest? {
|
||||
history.append(.originalRequest)
|
||||
return super.originalRequest
|
||||
}
|
||||
|
||||
override var currentRequest: URLRequest? {
|
||||
history.append(.currentRequest)
|
||||
return super.currentRequest
|
||||
}
|
||||
|
||||
override var response: URLResponse? {
|
||||
history.append(.response)
|
||||
return super.response
|
||||
}
|
||||
|
||||
@available(macOS 10.13, iOS 11.0, *)
|
||||
override var progress: Progress {
|
||||
history.append(.progress)
|
||||
return super.progress
|
||||
}
|
||||
|
||||
@available(macOS 10.13, iOS 11.0, *)
|
||||
override var earliestBeginDate: Date? {
|
||||
get {
|
||||
history.append(.getEarliestBeginDate)
|
||||
#if canImport(Darwin)
|
||||
return super.earliestBeginDate
|
||||
#else
|
||||
return nil // Deprecated in swift-corerlibs-foundation
|
||||
#endif
|
||||
}
|
||||
set {
|
||||
history.append(.setEarliestBeginDate(newValue))
|
||||
#if canImport(Darwin)
|
||||
super.earliestBeginDate = newValue
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.13, iOS 11.0, *)
|
||||
override var countOfBytesClientExpectsToSend: Int64 {
|
||||
get {
|
||||
history.append(.getCountOfBytesClientExpectsToSend)
|
||||
return super.countOfBytesClientExpectsToSend
|
||||
}
|
||||
set {
|
||||
history.append(.setCountOfBytesClientExpectsToSend(newValue))
|
||||
super.countOfBytesClientExpectsToSend = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.13, iOS 11.0, *)
|
||||
override var countOfBytesClientExpectsToReceive: Int64 {
|
||||
get {
|
||||
history.append(.getCountOfBytesClientExpectsToReceive)
|
||||
return super.countOfBytesClientExpectsToReceive
|
||||
}
|
||||
set {
|
||||
history.append(.setCountOfBytesClientExpectsToReceive(newValue))
|
||||
super.countOfBytesClientExpectsToReceive = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override var countOfBytesReceived: Int64 {
|
||||
history.append(.countOfBytesReceived)
|
||||
return super.countOfBytesReceived
|
||||
}
|
||||
|
||||
override var countOfBytesSent: Int64 {
|
||||
history.append(.countOfBytesSent)
|
||||
return super.countOfBytesSent
|
||||
}
|
||||
|
||||
override var countOfBytesExpectedToSend: Int64 {
|
||||
history.append(.countOfBytesExpectedToSend)
|
||||
return super.countOfBytesExpectedToSend
|
||||
}
|
||||
|
||||
override var countOfBytesExpectedToReceive: Int64 {
|
||||
history.append(.countOfBytesExpectedToReceive)
|
||||
return super.countOfBytesExpectedToReceive
|
||||
}
|
||||
|
||||
override var taskDescription: String? {
|
||||
get {
|
||||
history.append(.getTaskDescription)
|
||||
return super.taskDescription
|
||||
}
|
||||
set {
|
||||
history.append(.setTaskDescription(newValue))
|
||||
super.taskDescription = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
history.append(.cancel)
|
||||
}
|
||||
|
||||
override var state: URLSessionTask.State {
|
||||
history.append(.state)
|
||||
return super.state
|
||||
}
|
||||
|
||||
override var error: Error? {
|
||||
history.append(.error)
|
||||
return super.error
|
||||
}
|
||||
|
||||
override func suspend() {
|
||||
history.append(.suspend)
|
||||
}
|
||||
|
||||
override func resume() {
|
||||
history.append(.resume)
|
||||
}
|
||||
|
||||
override var priority: Float {
|
||||
get {
|
||||
history.append(.getPriority)
|
||||
return super.priority
|
||||
}
|
||||
set {
|
||||
history.append(.setPriority(newValue))
|
||||
super.priority = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension URLError: EquatableError {}
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func makePublisher(
|
||||
_ session: URLSession,
|
||||
_ url: URL
|
||||
) -> URLSession.DataTaskPublisher {
|
||||
return session.dataTaskPublisher(for: url)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func makePublisher(
|
||||
_ session: URLSession,
|
||||
_ request: URLRequest
|
||||
) -> URLSession.DataTaskPublisher {
|
||||
return session.dataTaskPublisher(for: request)
|
||||
}
|
||||
#else
|
||||
private func makePublisher(
|
||||
_ session: URLSession,
|
||||
_ url: URL
|
||||
) -> URLSession.OCombine.DataTaskPublisher {
|
||||
return session.ocombine.dataTaskPublisher(for: url)
|
||||
}
|
||||
|
||||
private func makePublisher(
|
||||
_ session: URLSession,
|
||||
_ request: URLRequest
|
||||
) -> URLSession.OCombine.DataTaskPublisher {
|
||||
return session.ocombine.dataTaskPublisher(for: request)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // canImport(Darwin)
|
||||
@@ -15,10 +15,10 @@ import OpenCombine
|
||||
|
||||
let childrenIsEmpty: (Mirror) -> Bool = { $0.children.isEmpty }
|
||||
|
||||
enum ExpectedMirrorChildValue: Equatable, ExpressibleByStringLiteral {
|
||||
enum ExpectedMirrorChildValue: ExpressibleByStringLiteral {
|
||||
case anything
|
||||
case matches(String)
|
||||
case contains(String)
|
||||
case matches(@autoclosure () -> String)
|
||||
case contains(@autoclosure () -> String)
|
||||
|
||||
typealias StringLiteralType = String
|
||||
|
||||
@@ -49,10 +49,11 @@ func expectedChildren(_ expectedChildren: (String?, ExpectedMirrorChildValue)...
|
||||
case (_, .anything):
|
||||
continue
|
||||
case let (lhs, .matches(rhs)):
|
||||
XCTAssertEqual(lhs, rhs, file: file, line: line)
|
||||
XCTAssertEqual(lhs, rhs(), file: file, line: line)
|
||||
case let (lhs, .contains(rhs)):
|
||||
XCTAssert(lhs.contains(rhs),
|
||||
"\"\(lhs)\" doesn't contain substring \"\(rhs)\"",
|
||||
let evaluatedRHS = rhs()
|
||||
XCTAssert(lhs.contains(evaluatedRHS),
|
||||
"\"\(lhs)\" doesn't contain substring \"\(evaluatedRHS)\"",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
@@ -133,7 +134,7 @@ internal func testSubscriptionReflection<Sut: Publisher>(
|
||||
customMirror customMirrorPredicate: ((Mirror) -> Bool)?,
|
||||
playgroundDescription: String,
|
||||
sut: Sut
|
||||
) throws where Sut.Output: Equatable {
|
||||
) throws {
|
||||
let tracking = TrackingSubscriberBase<Sut.Output, Sut.Failure>()
|
||||
sut.subscribe(tracking)
|
||||
|
||||
|
||||
@@ -40,6 +40,20 @@ extension TestingError: ExpressibleByStringLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
protocol EquatableError: Error {
|
||||
func isEqual(_ other: EquatableError) -> Bool
|
||||
}
|
||||
|
||||
extension EquatableError where Self: Equatable {
|
||||
func isEqual(_ other: EquatableError) -> Bool {
|
||||
return self == (other as? Self)
|
||||
}
|
||||
}
|
||||
|
||||
extension TestingError: EquatableError {}
|
||||
|
||||
extension NSError: EquatableError {}
|
||||
|
||||
func assertThrowsError<Result>(_ expression: @autoclosure () throws -> Result,
|
||||
_ expected: TestingError,
|
||||
_ message: @autoclosure () -> String = "",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@@ -177,6 +178,10 @@ final class TrackingSubscriberBase<Value, Failure: Error>
|
||||
for subscription in subscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
clearHistory()
|
||||
}
|
||||
|
||||
func clearHistory() {
|
||||
history = []
|
||||
}
|
||||
|
||||
@@ -220,8 +225,8 @@ extension TrackingSubscriberBase.Event {
|
||||
switch (lhs, rhs) {
|
||||
case (.finished, .finished):
|
||||
return true
|
||||
case let (.failure(lhs), .failure(rhs)):
|
||||
return (lhs as? TestingError) == (rhs as? TestingError)
|
||||
case let (.failure(lhs as EquatableError), .failure(rhs as EquatableError)):
|
||||
return lhs.isEqual(rhs)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -277,8 +282,9 @@ final class TrackingSubjectBase<Output: Equatable, Failure: Error>
|
||||
switch (lhs, rhs) {
|
||||
case (.finished, .finished):
|
||||
return true
|
||||
case let (.failure(lhs), .failure(rhs)):
|
||||
return (lhs as? TestingError) == (rhs as? TestingError)
|
||||
case let (.failure(lhs as EquatableError),
|
||||
.failure(rhs as EquatableError)):
|
||||
return lhs.isEqual(rhs)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -23,22 +23,27 @@ final class ObservableObjectPublisherTests: XCTestCase {
|
||||
receiveSubscription: { downstreamSubscription1 = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking1)
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject")])
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher")])
|
||||
downstreamSubscription1?.request(.max(1))
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject")])
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher")])
|
||||
publisher.send()
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal])
|
||||
publisher.send()
|
||||
publisher.send()
|
||||
downstreamSubscription1?.request(.max(3))
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal,
|
||||
.signal,
|
||||
.signal])
|
||||
publisher.send()
|
||||
publisher.send()
|
||||
publisher.send()
|
||||
publisher.send()
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
@@ -49,28 +54,48 @@ final class ObservableObjectPublisherTests: XCTestCase {
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
publisher.subscribe(tracking2)
|
||||
tracking2.assertHistoryEqual([.subscription("PassthroughSubject")])
|
||||
tracking2.assertHistoryEqual([.subscription("ObservableObjectPublisher")])
|
||||
|
||||
publisher.send()
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal])
|
||||
tracking2.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking2.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal])
|
||||
|
||||
downstreamSubscription1?.cancel()
|
||||
publisher.send()
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal,
|
||||
.signal])
|
||||
tracking2.assertHistoryEqual([.subscription("PassthroughSubject"),
|
||||
tracking2.assertHistoryEqual([.subscription("ObservableObjectPublisher"),
|
||||
.signal,
|
||||
.signal])
|
||||
|
||||
tracking1.cancel()
|
||||
tracking2.cancel()
|
||||
}
|
||||
|
||||
func testObservableObjectPublisherReflection() throws {
|
||||
try testSubscriptionReflection(
|
||||
description: "ObservableObjectPublisher",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase"))
|
||||
),
|
||||
playgroundDescription: "ObservableObjectPublisher",
|
||||
sut: ObservableObjectPublisher()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,11 @@ final class PublishedTests: XCTestCase {
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
testObject.objectWillChange.subscribe(tracking1)
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject")])
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher")])
|
||||
downstreamSubscription?.request(.max(2))
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject")])
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher")])
|
||||
testObject.state = 100
|
||||
tracking1.assertHistoryEqual([.subscription("PassthroughSubject")])
|
||||
tracking1.assertHistoryEqual([.subscription("ObservableObjectPublisher")])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// AssertNoFailureTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AssertNoFailureTests: XCTestCase {
|
||||
|
||||
func testPassThroughInput() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .max(2),
|
||||
createSut: { $0.assertNoFailure() })
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .max(2))
|
||||
XCTAssertEqual(helper.publisher.send(2), .max(2))
|
||||
XCTAssertEqual(helper.publisher.send(3), .max(2))
|
||||
helper.publisher.send(completion: .finished)
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
helper.publisher.send(subscription: subscription2)
|
||||
helper.publisher.send(subscription: subscription2)
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(42))
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CustomSubscription"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.completion(.finished),
|
||||
.completion(.finished),
|
||||
.subscription("CustomSubscription"),
|
||||
.subscription("CustomSubscription")])
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
|
||||
XCTAssertEqual(subscription2.history, [.requested(.max(42)),
|
||||
.cancelled,
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testCrashesOnFailure() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .max(2),
|
||||
createSut: { $0.assertNoFailure() })
|
||||
helper.publisher.send(completion: .finished)
|
||||
assertCrashes {
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
}
|
||||
|
||||
func testAssertNoFailureReflection() throws {
|
||||
try testReflection(
|
||||
parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "AssertNoFailure",
|
||||
customMirror: expectedChildren(
|
||||
("file", "SomeFile.swift"),
|
||||
("line", "1987"),
|
||||
("prefix", "PREFIX")
|
||||
),
|
||||
playgroundDescription: "AssertNoFailure",
|
||||
{ $0.assertNoFailure("PREFIX", file: "SomeFile.swift", line: 1987) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
//
|
||||
// CollectByCountTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CollectByCountTests: XCTestCase {
|
||||
|
||||
func testBasicBehavior() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(8))])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount")])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount")])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(4), .max(12))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.value([1, 2, 3, 4])])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
XCTAssertEqual(helper.publisher.send(6), .none)
|
||||
XCTAssertEqual(helper.publisher.send(7), .none)
|
||||
XCTAssertEqual(helper.publisher.send(8), .max(12))
|
||||
XCTAssertEqual(helper.publisher.send(9), .none)
|
||||
XCTAssertEqual(helper.publisher.send(10), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.value([1, 2, 3, 4]),
|
||||
.value([5, 6, 7, 8])])
|
||||
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.value([1, 2, 3, 4]),
|
||||
.value([5, 6, 7, 8]),
|
||||
.value([9, 10]),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(8))])
|
||||
}
|
||||
|
||||
func testFinishWithEmptyBuffer() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
XCTAssertEqual(helper.publisher.send(4), .max(12))
|
||||
helper.publisher.send(completion: .finished)
|
||||
helper.publisher.send(completion: .finished)
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
XCTAssertEqual(helper.publisher.send(6), .none)
|
||||
XCTAssertEqual(helper.publisher.send(7), .none)
|
||||
XCTAssertEqual(helper.publisher.send(8), .none)
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(2))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.value([1, 2, 3, 4]),
|
||||
.completion(.finished),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testFailureWithEmptyBuffer() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
XCTAssertEqual(helper.publisher.send(4), .max(12))
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
XCTAssertEqual(helper.publisher.send(6), .none)
|
||||
XCTAssertEqual(helper.publisher.send(7), .none)
|
||||
XCTAssertEqual(helper.publisher.send(8), .none)
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(2))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.value([1, 2, 3, 4]),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testFailureWithNonEmptyBuffer() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
XCTAssertEqual(helper.publisher.send(6), .none)
|
||||
XCTAssertEqual(helper.publisher.send(7), .none)
|
||||
XCTAssertEqual(helper.publisher.send(8), .none)
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(2))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testCrashesOnZeroDemand() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
|
||||
try assertCrashes {
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.none)
|
||||
}
|
||||
}
|
||||
|
||||
func testCancelThenFinish() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { $0.collect(4) })
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
helper.publisher.send(subscription: subscription2)
|
||||
XCTAssertEqual(subscription2.history, [.cancelled])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(2))
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("CollectByCount"),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testCollectByCountReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(value: 42,
|
||||
expected: .history([], demand: .none),
|
||||
{ $0.collect(19) })
|
||||
}
|
||||
|
||||
func testCollectByCountReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.completion(.finished)]),
|
||||
{ $0.collect(19) }
|
||||
)
|
||||
}
|
||||
|
||||
func testCollectByCountRequestBeforeSubscription() {
|
||||
testRequestBeforeSubscription(inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.collect(19) })
|
||||
}
|
||||
|
||||
func testCollectByCountCancelBeforeSubscription() {
|
||||
testCancelBeforeSubscription(inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.collect(19) })
|
||||
}
|
||||
|
||||
func testCollectByCountReceiveSubscriptionTwice() throws {
|
||||
try testReceiveSubscriptionTwice { $0.collect(19) }
|
||||
}
|
||||
|
||||
func testCollectByCountLifecycle() throws {
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ $0.collect(42) })
|
||||
}
|
||||
|
||||
func testCollectByCountReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "CollectByCount",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("upstreamSubscription", "nil"),
|
||||
("buffer", "[]"),
|
||||
("count", "53")
|
||||
),
|
||||
playgroundDescription: "CollectByCount",
|
||||
{ $0.collect(53) })
|
||||
}
|
||||
}
|
||||
@@ -359,9 +359,21 @@ final class ConcatenateTests: XCTestCase {
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
|
||||
assertCrashes {
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Concatenate"),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
}
|
||||
|
||||
func testHelperMethods() {
|
||||
|
||||
@@ -18,7 +18,7 @@ final class DelayTests: XCTestCase {
|
||||
|
||||
// Delay's Inner doesn't conform to CustomStringConvertible, so we can't compare
|
||||
// subscriptions using their descriptions
|
||||
let delaySubscription: StringSubscription = {
|
||||
private let delaySubscription: StringSubscription = {
|
||||
let tracking = TrackingSubscriber()
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
CustomPublisher(subscription: CustomSubscription())
|
||||
@@ -29,6 +29,15 @@ final class DelayTests: XCTestCase {
|
||||
?? "Delay"
|
||||
}()
|
||||
|
||||
private let delaySubscriptionImmediateScheduler: StringSubscription = {
|
||||
let tracking = TrackingSubscriber()
|
||||
CustomPublisher(subscription: CustomSubscription())
|
||||
.delay(for: 0, scheduler: ImmediateScheduler.shared)
|
||||
.subscribe(tracking)
|
||||
return tracking.subscriptions.first.map(StringSubscription.subscription)
|
||||
?? "Delay"
|
||||
}()
|
||||
|
||||
func testBasicBehavior() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
@@ -41,15 +50,15 @@ final class DelayTests: XCTestCase {
|
||||
}
|
||||
XCTAssertNotNil(helper.publisher.subscriber)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100))])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
scheduler.executeScheduledActions()
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100))])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
@@ -62,8 +71,7 @@ final class DelayTests: XCTestCase {
|
||||
.nanoseconds(200)])
|
||||
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.schedule(options: .nontrivialOptions),
|
||||
.now,
|
||||
[.now,
|
||||
.scheduleAfterDate(.nanoseconds(200),
|
||||
tolerance: .nanoseconds(5),
|
||||
options: .nontrivialOptions),
|
||||
@@ -89,8 +97,7 @@ final class DelayTests: XCTestCase {
|
||||
.requested(.max(12))])
|
||||
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.schedule(options: .nontrivialOptions),
|
||||
.now,
|
||||
[.now,
|
||||
.scheduleAfterDate(.nanoseconds(200),
|
||||
tolerance: .nanoseconds(5),
|
||||
options: .nontrivialOptions),
|
||||
@@ -117,8 +124,7 @@ final class DelayTests: XCTestCase {
|
||||
.requested(.max(12))])
|
||||
XCTAssertEqual(scheduler.scheduledDates, [.nanoseconds(400)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.schedule(options: .nontrivialOptions),
|
||||
.now,
|
||||
[.now,
|
||||
.scheduleAfterDate(.nanoseconds(200),
|
||||
tolerance: .nanoseconds(5),
|
||||
options: .nontrivialOptions),
|
||||
@@ -146,8 +152,7 @@ final class DelayTests: XCTestCase {
|
||||
.requested(.max(12)),
|
||||
.requested(.max(12))])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.schedule(options: .nontrivialOptions),
|
||||
.now,
|
||||
[.now,
|
||||
.scheduleAfterDate(.nanoseconds(200),
|
||||
tolerance: .nanoseconds(5),
|
||||
options: .nontrivialOptions),
|
||||
@@ -223,14 +228,14 @@ final class DelayTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(0), .none)
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
}
|
||||
|
||||
func testReceiveCompletionImmediatelyAfterSubscription() {
|
||||
@@ -246,19 +251,19 @@ final class DelayTests: XCTestCase {
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.schedule(options: .nontrivialOptions),
|
||||
.now,
|
||||
[.now,
|
||||
.scheduleAfterDate(.nanoseconds(123),
|
||||
tolerance: .nanoseconds(5),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
scheduler.executeScheduledActions()
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(delaySubscription),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testReceiveCompletionImmediatelyAfterValue() {
|
||||
@@ -282,8 +287,7 @@ final class DelayTests: XCTestCase {
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(418))])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.schedule(options: .nontrivialOptions),
|
||||
.now,
|
||||
[.now,
|
||||
.scheduleAfterDate(.nanoseconds(123),
|
||||
tolerance: .nanoseconds(5),
|
||||
options: .nontrivialOptions),
|
||||
@@ -313,13 +317,29 @@ final class DelayTests: XCTestCase {
|
||||
$0.delay(for: .nanoseconds(123), scheduler: ImmediateScheduler.shared)
|
||||
}
|
||||
|
||||
var recursionCounter = 5
|
||||
helper.tracking.onValue = { _ in
|
||||
if recursionCounter == 0 { return }
|
||||
recursionCounter -= 1
|
||||
_ = helper.publisher.send(-1)
|
||||
}
|
||||
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(0)
|
||||
}
|
||||
XCTAssertEqual(helper.publisher.send(0), .none)
|
||||
XCTAssertEqual(helper.tracking.history,
|
||||
[.subscription(delaySubscriptionImmediateScheduler),
|
||||
.value(0),
|
||||
.value(-1),
|
||||
.value(-1),
|
||||
.value(-1),
|
||||
.value(-1),
|
||||
.value(-1)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418))])
|
||||
}
|
||||
|
||||
func testReceiveCompletionRecursively() {
|
||||
@@ -334,27 +354,7 @@ final class DelayTests: XCTestCase {
|
||||
helper.publisher.send(completion: .finished)
|
||||
}
|
||||
|
||||
func testWeakCaptureWhenSchedulingSubscription() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var subscription: Subscription?
|
||||
var subscriberReleased = false
|
||||
do {
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
let delay = publisher.delay(for: 0.35, scheduler: scheduler)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { subscription = $0 },
|
||||
onDeinit: { subscriberReleased = true })
|
||||
delay.subscribe(tracking)
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
XCTAssertEqual(scheduler.history, [.minimumTolerance,
|
||||
.schedule(options: nil)])
|
||||
publisher.cancel()
|
||||
}
|
||||
XCTAssertTrue(subscriberReleased)
|
||||
scheduler.executeScheduledActions()
|
||||
XCTAssertNil(subscription)
|
||||
}
|
||||
|
||||
func testWeakCaptureWhenSchedulingValue() {
|
||||
func testStrongCaptureWhenSchedulingValue() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var value: Int?
|
||||
var subscriberReleased = false
|
||||
@@ -370,7 +370,6 @@ final class DelayTests: XCTestCase {
|
||||
XCTAssertEqual(tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.minimumTolerance,
|
||||
.schedule(options: nil),
|
||||
.now,
|
||||
.scheduleAfterDate(.seconds(0.35),
|
||||
tolerance: 0,
|
||||
@@ -380,11 +379,11 @@ final class DelayTests: XCTestCase {
|
||||
}
|
||||
XCTAssertFalse(subscriberReleased)
|
||||
scheduler.executeScheduledActions()
|
||||
XCTAssertNil(value)
|
||||
XCTAssertEqual(value, 42)
|
||||
XCTAssertTrue(subscriberReleased)
|
||||
}
|
||||
|
||||
func testWeakCaptureWhenSchedulingCompletion() {
|
||||
func testStrongCaptureWhenSchedulingCompletion() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var completion: Subscribers.Completion<TestingError>?
|
||||
var subscriberReleased = false
|
||||
@@ -400,7 +399,6 @@ final class DelayTests: XCTestCase {
|
||||
XCTAssertEqual(tracking.history, [.subscription(delaySubscription)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.minimumTolerance,
|
||||
.schedule(options: nil),
|
||||
.now,
|
||||
.scheduleAfterDate(.seconds(0.35),
|
||||
tolerance: 0,
|
||||
@@ -410,7 +408,7 @@ final class DelayTests: XCTestCase {
|
||||
}
|
||||
XCTAssertFalse(subscriberReleased)
|
||||
scheduler.executeScheduledActions()
|
||||
XCTAssertNil(completion)
|
||||
XCTAssertEqual(completion, .finished)
|
||||
XCTAssertTrue(subscriberReleased)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// DropUntilOutputTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.12.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class DropUntilOutputTests: XCTestCase {
|
||||
|
||||
func testOtherCompletesBeforeTriggering() {
|
||||
let otherSubscription = CustomSubscription()
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(100),
|
||||
receiveValueDemand: .max(5),
|
||||
createSut: { $0.drop(untilOutputFrom: otherPublisher) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100))])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
|
||||
otherPublisher.send(completion: .finished)
|
||||
otherPublisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput"),
|
||||
.completion(.finished),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100)), .cancelled])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
XCTAssertEqual(otherPublisher.send(1000), .none)
|
||||
XCTAssertEqual(helper.publisher.send(4), .max(5))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput"),
|
||||
.completion(.finished),
|
||||
.completion(.finished),
|
||||
.value(4)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100)), .cancelled])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
}
|
||||
|
||||
func testOtherFailsAfterTriggering() {
|
||||
let otherSubscription = CustomSubscription()
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .max(5),
|
||||
createSut: { $0.drop(untilOutputFrom: otherPublisher) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(otherPublisher.send(1000), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .max(5))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput"),
|
||||
.value(2)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2))])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
|
||||
otherPublisher.send(completion: .failure(.oops))
|
||||
otherPublisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput"),
|
||||
.value(2)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2))])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
}
|
||||
|
||||
func testDemand() throws {
|
||||
let subscription = CustomSubscription()
|
||||
let otherSubscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let dropUntilOutput = publisher.drop(untilOutputFrom: otherPublisher)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { subscription in
|
||||
downstreamSubscription = subscription
|
||||
},
|
||||
receiveValue: { .max($0) }
|
||||
)
|
||||
dropUntilOutput.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropUntilOutput")])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(4))
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(4))])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropUntilOutput")])
|
||||
|
||||
XCTAssertEqual(publisher.send(1), .none)
|
||||
XCTAssertEqual(publisher.send(2), .none)
|
||||
XCTAssertEqual(otherPublisher.send(1000), .none)
|
||||
XCTAssertEqual(publisher.send(3), .max(3))
|
||||
XCTAssertEqual(publisher.send(4), .max(4))
|
||||
XCTAssertEqual(publisher.send(5), .max(5))
|
||||
XCTAssertEqual(publisher.send(6), .max(6))
|
||||
XCTAssertEqual(publisher.send(7), .max(7))
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(4))])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropUntilOutput"),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7)])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
let otherSubscription = CustomSubscription()
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .max(5),
|
||||
createSut: { $0.drop(untilOutputFrom: otherPublisher) }
|
||||
)
|
||||
|
||||
helper.subscription.onCancel = {
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
}
|
||||
|
||||
otherSubscription.onCancel = {
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2)), .cancelled])
|
||||
}
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(10))
|
||||
XCTAssertEqual(helper.publisher.send(1000), .none)
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
helper.publisher.send(subscription: subscription2)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2)), .cancelled])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1)), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput")])
|
||||
XCTAssertEqual(subscription2.history, [.cancelled])
|
||||
}
|
||||
|
||||
func testSubscribesToOtherFirst() {
|
||||
let subscription = CustomSubscription()
|
||||
let otherSubscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let dropUntilOutput = publisher.drop(untilOutputFrom: otherPublisher)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { _ in
|
||||
XCTAssertNotNil(publisher.subscriber)
|
||||
XCTAssertNotNil(otherPublisher.subscriber)
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
}
|
||||
)
|
||||
|
||||
otherPublisher.willSubscribe = { _ in
|
||||
XCTAssertNil(publisher.subscriber)
|
||||
}
|
||||
|
||||
publisher.willSubscribe = { _ in
|
||||
XCTAssertNotNil(otherPublisher.subscriber)
|
||||
}
|
||||
|
||||
dropUntilOutput.subscribe(tracking)
|
||||
tracking.cancel()
|
||||
}
|
||||
|
||||
func testSubscribersHaveTheSameCombineIdentifier() {
|
||||
let subscription = CustomSubscription()
|
||||
let otherSubscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let dropUntilOutput = publisher.drop(untilOutputFrom: otherPublisher)
|
||||
let tracking = TrackingSubscriber()
|
||||
dropUntilOutput.subscribe(tracking)
|
||||
|
||||
XCTAssert(publisher.erasedSubscriber is CustomCombineIdentifierConvertible)
|
||||
XCTAssert(otherPublisher.erasedSubscriber is CustomCombineIdentifierConvertible)
|
||||
XCTAssertEqual(
|
||||
(publisher.erasedSubscriber as? CustomCombineIdentifierConvertible)?
|
||||
.combineIdentifier,
|
||||
(otherPublisher.erasedSubscriber as? CustomCombineIdentifierConvertible)?
|
||||
.combineIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
func testLateSubscription() throws {
|
||||
|
||||
// This publisher doesn't send a subscription when it receives a subscriber
|
||||
let publisher = CustomPublisher(subscription: nil)
|
||||
let dropUntilOutput = publisher.drop(untilOutputFrom: Empty<Void, TestingError>())
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(10))
|
||||
$0.request(.max(4))
|
||||
$0.request(.none)
|
||||
}
|
||||
)
|
||||
|
||||
dropUntilOutput.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.completion(.finished),
|
||||
.subscription("DropUntilOutput")])
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
try XCTUnwrap(publisher.subscriber).receive(subscription: subscription)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(14))])
|
||||
XCTAssertEqual(tracking.history, [.completion(.finished),
|
||||
.subscription("DropUntilOutput")])
|
||||
}
|
||||
|
||||
func testReusableOtherSubscriber() throws {
|
||||
let otherSubscription = CustomSubscription()
|
||||
let otherPublisher = CustomPublisher(subscription: otherSubscription)
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .max(5),
|
||||
createSut: { $0.drop(untilOutputFrom: otherPublisher) }
|
||||
)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
try XCTUnwrap(otherPublisher.subscriber).receive(subscription: subscription2)
|
||||
|
||||
XCTAssertEqual(subscription2.history, [.cancelled])
|
||||
XCTAssertEqual(otherPublisher.send(1000), .none)
|
||||
|
||||
let subscription3 = CustomSubscription()
|
||||
try XCTUnwrap(otherPublisher.subscriber).receive(subscription: subscription3)
|
||||
|
||||
XCTAssertEqual(subscription3.history, [.requested(.max(1))])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(subscription3.history, [.requested(.max(1)),
|
||||
.cancelled])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2)), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("DropUntilOutput")])
|
||||
}
|
||||
|
||||
func testCrashesWhenReceivesInputAfterCancel() {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.drop(untilOutputFrom: Empty<Void, TestingError>()) }
|
||||
)
|
||||
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(0)
|
||||
}
|
||||
}
|
||||
|
||||
func testDropUntilOutputReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(
|
||||
value: 42,
|
||||
expected: .crash,
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, Never>()) }
|
||||
)
|
||||
}
|
||||
|
||||
func testDropUntilOutputOtherReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(
|
||||
value: 42,
|
||||
expected: .history([.completion(.finished), .subscription("DropUntilOutput")],
|
||||
demand: .none),
|
||||
{ Empty<Int, Never>().drop(untilOutputFrom: $0) }
|
||||
)
|
||||
}
|
||||
|
||||
func testDropUntilOutputReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.completion(.finished),
|
||||
.subscription("DropUntilOutput"),
|
||||
.completion(.finished)]),
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, Never>()) }
|
||||
)
|
||||
}
|
||||
|
||||
func testDropUntilOutputOtherReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.completion(.finished),
|
||||
.subscription("DropUntilOutput"),
|
||||
.completion(.finished)]),
|
||||
{ Empty<Int, Never>().drop(untilOutputFrom: $0) }
|
||||
)
|
||||
}
|
||||
|
||||
func testDropUntilOutputRequestBeforeSubscription() {
|
||||
testRequestBeforeSubscription(inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, Never>()) })
|
||||
}
|
||||
|
||||
func testDropUntilOutputCancelBeforeSubscription() {
|
||||
testCancelBeforeSubscription(inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, Never>()) })
|
||||
}
|
||||
|
||||
func testDropUntilOutputReceiveSubscriptionTwice() throws {
|
||||
try testReceiveSubscriptionTwice {
|
||||
$0.drop(untilOutputFrom: Empty<Int, TestingError>())
|
||||
}
|
||||
}
|
||||
|
||||
func testDropUntilOutputLifecycle() throws {
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, TestingError>()) })
|
||||
}
|
||||
|
||||
func testDropUntilOutputOtherLifecycle() throws {
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ Empty<Int, TestingError>().drop(untilOutputFrom: $0) })
|
||||
}
|
||||
|
||||
func testDropUntilOutputReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "DropUntilOutput",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "DropUntilOutput",
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, TestingError>()) })
|
||||
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "DropUntilOutput",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "DropUntilOutput",
|
||||
{ Empty<Int, TestingError>().drop(untilOutputFrom: $0) })
|
||||
}
|
||||
}
|
||||
@@ -390,11 +390,15 @@ final class FlatMapTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testChildValueReceivedWhileSendingValue() throws {
|
||||
let upstreamPublisher = PassthroughSubject<AnyPublisher<Int, TestingError>,
|
||||
TestingError>()
|
||||
let upstreamSubscription = CustomSubscription()
|
||||
let upstreamPublisher = CustomPublisherBase<CustomPublisher, TestingError>(
|
||||
subscription: upstreamSubscription
|
||||
)
|
||||
|
||||
let child1Publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
let child2Publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
let childSubscription1 = CustomSubscription()
|
||||
let childSubscription2 = CustomSubscription()
|
||||
let child1Publisher = CustomPublisher(subscription: childSubscription1)
|
||||
let child2Publisher = CustomPublisher(subscription: childSubscription2)
|
||||
|
||||
let flatMap = upstreamPublisher.flatMap { $0 }
|
||||
|
||||
@@ -408,12 +412,17 @@ final class FlatMapTests: XCTestCase {
|
||||
|
||||
flatMap.subscribe(downstreamSubscriber)
|
||||
|
||||
upstreamPublisher.send(AnyPublisher(child1Publisher))
|
||||
upstreamPublisher.send(AnyPublisher(child2Publisher))
|
||||
XCTAssertEqual(upstreamPublisher.send(child1Publisher), .none)
|
||||
XCTAssertEqual(upstreamPublisher.send(child2Publisher), .none)
|
||||
|
||||
assertCrashes {
|
||||
XCTAssertEqual(child1Publisher.send(666), .max(1))
|
||||
}
|
||||
XCTAssertEqual(child1Publisher.send(666), .max(1))
|
||||
|
||||
XCTAssertEqual(upstreamSubscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
|
||||
.value(666),
|
||||
.value(777)])
|
||||
XCTAssertEqual(childSubscription1.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(childSubscription2.history, [.requested(.max(1))])
|
||||
}
|
||||
|
||||
func testOuterLockReentrance() {
|
||||
@@ -462,9 +471,6 @@ final class FlatMapTests: XCTestCase {
|
||||
// Create some downstream demand
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(5))
|
||||
|
||||
// If Apple changes the implementation to use recursive lock,
|
||||
// we must make sure no stack overflow occurs here,
|
||||
// which will also be detected as a crash, which is not what we want.
|
||||
var recursionDepth = 10
|
||||
helper.tracking.onFailure = { _ in
|
||||
if recursionDepth <= 0 {
|
||||
@@ -474,10 +480,13 @@ final class FlatMapTests: XCTestCase {
|
||||
_ = child.send(1)
|
||||
}
|
||||
|
||||
// Expected deadlock
|
||||
assertCrashes {
|
||||
child.send(completion: .failure(.oops))
|
||||
}
|
||||
child.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("FlatMap"),
|
||||
.completion(.failure(.oops)),
|
||||
.value(1)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(childSubscription.history, [.requested(.max(1))])
|
||||
}
|
||||
|
||||
func testCompletesProperlyWhenUpstreamOutlivesChildren() {
|
||||
|
||||
@@ -210,10 +210,15 @@ final class MapErrorTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private struct OtherError: Error {
|
||||
let original: Error
|
||||
private struct OtherError: EquatableError {
|
||||
let original: EquatableError
|
||||
|
||||
init(_ original: Error) {
|
||||
init(_ original: EquatableError) {
|
||||
self.original = original
|
||||
}
|
||||
|
||||
func isEqual(_ other: EquatableError) -> Bool {
|
||||
guard let other = other as? OtherError else { return false }
|
||||
return original.isEqual(other.original)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
//
|
||||
// MergeTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 06.01.2020.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class MergeTests: XCTestCase {
|
||||
|
||||
private func createTestPublishers(
|
||||
arity: Int
|
||||
) -> ([CustomSubscription], [CustomPublisher]) {
|
||||
precondition(arity >= 0)
|
||||
let subscriptions = (0 ..< arity).map { _ in
|
||||
CustomSubscription()
|
||||
}
|
||||
let publishers = (0 ..< arity).map {
|
||||
CustomPublisher(subscription: subscriptions[$0])
|
||||
}
|
||||
return (subscriptions, publishers)
|
||||
}
|
||||
|
||||
func testMergeLimitedInitialDemand() {
|
||||
func test<Merger: Publisher>(
|
||||
forArity arity: Int,
|
||||
_ makeMerger: ([CustomPublisher]) -> Merger
|
||||
) where Merger.Output == Int, Merger.Failure == TestingError {
|
||||
let (subscriptions, publishers) = createTestPublishers(arity: arity)
|
||||
let merger = makeMerger(publishers)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 },
|
||||
receiveValue: { _ in .max(3) }
|
||||
)
|
||||
merger.subscribe(tracking)
|
||||
|
||||
for (i, subscription) in subscriptions.enumerated() {
|
||||
XCTAssertEqual(subscription.history,
|
||||
[.requested(.max(1))],
|
||||
"failure for subscription \(i)")
|
||||
}
|
||||
XCTAssertEqual(tracking.history, [.subscription("Merge")])
|
||||
|
||||
// No downstream demand, these values are buffered
|
||||
for (i, publisher) in publishers.reversed().enumerated() {
|
||||
XCTAssertEqual(publisher.send(-i), .none) // ignored
|
||||
XCTAssertEqual(publisher.send(i), .none)
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Merge")])
|
||||
|
||||
// Establishing downstream demand
|
||||
downstreamSubscription?.request(.max(arity))
|
||||
downstreamSubscription?.request(.max(arity))
|
||||
|
||||
for (i, subscription) in subscriptions.enumerated() {
|
||||
XCTAssertEqual(subscription.history,
|
||||
[.requested(.max(1)),
|
||||
.requested(.max(1))],
|
||||
"failure for subscription \(i)")
|
||||
}
|
||||
let expectedValues: [TrackingSubscriber.Event] = (0 ..< arity)
|
||||
.reversed()
|
||||
.map {
|
||||
.value($0)
|
||||
}
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("Merge")] + expectedValues)
|
||||
|
||||
// Requesting more elements
|
||||
downstreamSubscription?.request(.max(arity))
|
||||
|
||||
// Satisfying the unfullfilled demand
|
||||
for (i, publisher) in publishers.reversed().enumerated() {
|
||||
XCTAssertEqual(publisher.send(i), .max(1))
|
||||
}
|
||||
|
||||
XCTAssertEqual(
|
||||
tracking.history,
|
||||
[.subscription("Merge")] + expectedValues + expectedValues.reversed()
|
||||
)
|
||||
|
||||
tracking.cancel()
|
||||
}
|
||||
|
||||
test(forArity: 2) { publishers in
|
||||
Publishers.Merge(publishers[0],
|
||||
publishers[1])
|
||||
}
|
||||
|
||||
test(forArity: 3) { publishers in
|
||||
Publishers.Merge3(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2])
|
||||
}
|
||||
|
||||
test(forArity: 4) { publishers in
|
||||
Publishers.Merge4(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3])
|
||||
}
|
||||
|
||||
test(forArity: 4) { publishers in
|
||||
Publishers.Merge4(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3])
|
||||
}
|
||||
|
||||
test(forArity: 5) { publishers in
|
||||
Publishers.Merge5(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4])
|
||||
}
|
||||
|
||||
test(forArity: 6) { publishers in
|
||||
Publishers.Merge6(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4],
|
||||
publishers[5])
|
||||
}
|
||||
|
||||
test(forArity: 7) { publishers in
|
||||
Publishers.Merge7(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4],
|
||||
publishers[5],
|
||||
publishers[6])
|
||||
}
|
||||
|
||||
test(forArity: 8) { publishers in
|
||||
Publishers.Merge8(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4],
|
||||
publishers[5],
|
||||
publishers[6],
|
||||
publishers[7])
|
||||
}
|
||||
|
||||
test(forArity: 0) { _ in
|
||||
Publishers.MergeMany<CustomPublisher>()
|
||||
}
|
||||
|
||||
test(forArity: 2) { publishers in
|
||||
Publishers.MergeMany(publishers[0], publishers[1])
|
||||
}
|
||||
|
||||
test(forArity: 20) { publishers in
|
||||
Publishers.MergeMany(publishers)
|
||||
}
|
||||
}
|
||||
|
||||
func testMergeUnimitedInitialDemand() {
|
||||
func test<Merger: Publisher>(
|
||||
forArity arity: Int,
|
||||
_ makeMerger: ([CustomPublisher]) -> Merger
|
||||
) where Merger.Output == Int, Merger.Failure == TestingError {
|
||||
let (subscriptions, publishers) = createTestPublishers(arity: arity)
|
||||
let merger = makeMerger(publishers)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
},
|
||||
receiveValue: { _ in .max(3) }
|
||||
)
|
||||
merger.subscribe(tracking)
|
||||
|
||||
for (i, subscription) in subscriptions.enumerated() {
|
||||
XCTAssertEqual(subscription.history,
|
||||
[.requested(.max(1)),
|
||||
.requested(.unlimited)],
|
||||
"failure for subscription \(i)")
|
||||
}
|
||||
|
||||
if arity == 0 {
|
||||
XCTAssertEqual(tracking.history, [.subscription("Merge"),
|
||||
.completion(.finished)])
|
||||
} else {
|
||||
XCTAssertEqual(tracking.history, [.subscription("Merge")])
|
||||
}
|
||||
|
||||
downstreamSubscription?.request(.max(42))
|
||||
downstreamSubscription?.request(.unlimited)
|
||||
|
||||
for (i, subscription) in subscriptions.enumerated() {
|
||||
XCTAssertEqual(subscription.history,
|
||||
[.requested(.max(1)),
|
||||
.requested(.unlimited)],
|
||||
"failure for subscription \(i)")
|
||||
}
|
||||
|
||||
for (i, publisher) in publishers.reversed().enumerated() {
|
||||
XCTAssertEqual(publisher.send(i), .max(3))
|
||||
}
|
||||
|
||||
let expectedValues: [TrackingSubscriber.Event] = (0 ..< arity).map {
|
||||
.value($0)
|
||||
}
|
||||
|
||||
if arity == 0 {
|
||||
XCTAssertEqual(tracking.history, [.subscription("Merge"),
|
||||
.completion(.finished)])
|
||||
} else {
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("Merge")] + expectedValues)
|
||||
}
|
||||
|
||||
|
||||
for (i, publisher) in publishers.enumerated() {
|
||||
XCTAssertEqual(publisher.send(i), .max(3))
|
||||
}
|
||||
|
||||
if arity == 0 {
|
||||
XCTAssertEqual(tracking.history, [.subscription("Merge"),
|
||||
.completion(.finished)])
|
||||
} else {
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("Merge")] + expectedValues + expectedValues)
|
||||
}
|
||||
|
||||
tracking.cancel()
|
||||
}
|
||||
|
||||
test(forArity: 2) { publishers in
|
||||
Publishers.Merge(publishers[0],
|
||||
publishers[1])
|
||||
}
|
||||
|
||||
test(forArity: 3) { publishers in
|
||||
Publishers.Merge3(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2])
|
||||
}
|
||||
|
||||
test(forArity: 4) { publishers in
|
||||
Publishers.Merge4(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3])
|
||||
}
|
||||
|
||||
test(forArity: 4) { publishers in
|
||||
Publishers.Merge4(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3])
|
||||
}
|
||||
|
||||
test(forArity: 5) { publishers in
|
||||
Publishers.Merge5(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4])
|
||||
}
|
||||
|
||||
test(forArity: 6) { publishers in
|
||||
Publishers.Merge6(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4],
|
||||
publishers[5])
|
||||
}
|
||||
|
||||
test(forArity: 7) { publishers in
|
||||
Publishers.Merge7(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4],
|
||||
publishers[5],
|
||||
publishers[6])
|
||||
}
|
||||
|
||||
test(forArity: 8) { publishers in
|
||||
Publishers.Merge8(publishers[0],
|
||||
publishers[1],
|
||||
publishers[2],
|
||||
publishers[3],
|
||||
publishers[4],
|
||||
publishers[5],
|
||||
publishers[6],
|
||||
publishers[7])
|
||||
}
|
||||
|
||||
test(forArity: 0) { _ in
|
||||
Publishers.MergeMany<CustomPublisher>()
|
||||
}
|
||||
|
||||
test(forArity: 2) { publishers in
|
||||
Publishers.MergeMany(publishers[0], publishers[1])
|
||||
}
|
||||
|
||||
test(forArity: 20) { publishers in
|
||||
Publishers.MergeMany(publishers)
|
||||
}
|
||||
}
|
||||
|
||||
func testMergeReflection() throws {
|
||||
func testMergeSubscriptionReflection<Sut: Publisher>(_ sut: Sut) throws {
|
||||
try testSubscriptionReflection(
|
||||
description: "Merge",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "Merge",
|
||||
sut: sut
|
||||
)
|
||||
}
|
||||
func testMergeSideReflection<Merger: Publisher>(
|
||||
_ makeMerger: (CustomPublisher) -> Merger
|
||||
) throws where Merger.Output == Int, Merger.Failure == TestingError {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "Merge",
|
||||
customMirror: expectedChildren(
|
||||
("parentSubscription", .anything)
|
||||
),
|
||||
playgroundDescription: "Merge",
|
||||
makeMerger)
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
let merger = makeMerger(publisher)
|
||||
let tracking = TrackingSubscriber()
|
||||
merger.subscribe(tracking)
|
||||
let side = try XCTUnwrap(publisher.erasedSubscriber)
|
||||
let expectedParentID =
|
||||
try XCTUnwrap(tracking.subscriptions.first?.combineIdentifier)
|
||||
let actualParentID = Mirror(reflecting: side)
|
||||
.descendant("parentSubscription") as? CombineIdentifier
|
||||
XCTAssertEqual(expectedParentID, actualParentID)
|
||||
}
|
||||
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher) as Publishers.Merge
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher) as Publishers.Merge
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher,
|
||||
publisher) as Publishers.Merge3
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher,
|
||||
publisher) as Publishers.Merge3
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge4
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge4
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge5
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge5
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge6
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge6
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge7
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge7
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge8
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher,
|
||||
publisher) as Publishers.Merge8
|
||||
}
|
||||
|
||||
try testMergeSubscriptionReflection(
|
||||
publisher.merge(with: publisher) as Publishers.MergeMany
|
||||
)
|
||||
try testMergeSideReflection {
|
||||
$0.merge(with: $0) as Publishers.MergeMany
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,15 +26,15 @@ final class ReceiveOnTests: XCTestCase {
|
||||
XCTAssertNotNil(helper.publisher.subscriber,
|
||||
"Subscription must be performed synchronously")
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100))])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
scheduler.executeScheduledActions()
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(100))])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
@@ -47,7 +47,6 @@ final class ReceiveOnTests: XCTestCase {
|
||||
.nanoseconds(0)])
|
||||
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions)])
|
||||
|
||||
@@ -64,7 +63,6 @@ final class ReceiveOnTests: XCTestCase {
|
||||
.requested(.max(12))])
|
||||
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions)])
|
||||
|
||||
@@ -82,7 +80,6 @@ final class ReceiveOnTests: XCTestCase {
|
||||
.requested(.max(12))])
|
||||
XCTAssertEqual(scheduler.scheduledDates, [.nanoseconds(0)])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions)])
|
||||
@@ -97,7 +94,6 @@ final class ReceiveOnTests: XCTestCase {
|
||||
.requested(.max(12)),
|
||||
.requested(.max(12))])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions),
|
||||
.schedule(options: .nontrivialOptions)])
|
||||
@@ -155,14 +151,14 @@ final class ReceiveOnTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(0), .none)
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
}
|
||||
|
||||
func testReceiveCompletionImmediatelyAfterSubscription() {
|
||||
@@ -175,15 +171,15 @@ final class ReceiveOnTests: XCTestCase {
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil),
|
||||
.schedule(options: nil)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil)])
|
||||
|
||||
scheduler.executeScheduledActions()
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn"),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testReceiveCompletionImmediatelyAfterValue() {
|
||||
@@ -204,7 +200,6 @@ final class ReceiveOnTests: XCTestCase {
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(418))])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil),
|
||||
.schedule(options: nil),
|
||||
.schedule(options: nil),
|
||||
.schedule(options: nil)])
|
||||
|
||||
@@ -218,20 +213,35 @@ final class ReceiveOnTests: XCTestCase {
|
||||
.requested(.max(418))])
|
||||
}
|
||||
|
||||
func testCrashesWhenReceivingInputRecursively() {
|
||||
func testReceiveInputRecursively() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .max(418)) {
|
||||
$0.receive(on: ImmediateScheduler.shared)
|
||||
}
|
||||
|
||||
var recursionCounter = 5
|
||||
helper.tracking.onValue = { _ in
|
||||
if recursionCounter == 0 { return }
|
||||
recursionCounter -= 1
|
||||
_ = helper.publisher.send(-1)
|
||||
}
|
||||
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(0)
|
||||
}
|
||||
XCTAssertEqual(helper.publisher.send(0), .none)
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReceiveOn"),
|
||||
.value(0),
|
||||
.value(-1),
|
||||
.value(-1),
|
||||
.value(-1),
|
||||
.value(-1),
|
||||
.value(-1)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418)),
|
||||
.requested(.max(418))])
|
||||
}
|
||||
|
||||
func testReceiveCompletionRecursively() {
|
||||
@@ -246,25 +256,6 @@ final class ReceiveOnTests: XCTestCase {
|
||||
helper.publisher.send(completion: .finished)
|
||||
}
|
||||
|
||||
func testWeakCaptureWhenSchedulingSubscription() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var subscription: Subscription?
|
||||
var subscriberReleased = false
|
||||
do {
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
let receiveOn = publisher.receive(on: scheduler)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { subscription = $0 },
|
||||
onDeinit: { subscriberReleased = true })
|
||||
receiveOn.subscribe(tracking)
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil)])
|
||||
publisher.cancel()
|
||||
}
|
||||
XCTAssertTrue(subscriberReleased)
|
||||
scheduler.executeScheduledActions()
|
||||
XCTAssertNil(subscription)
|
||||
}
|
||||
|
||||
func testWeakCaptureWhenSchedulingValue() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var value: Int?
|
||||
@@ -279,8 +270,7 @@ final class ReceiveOnTests: XCTestCase {
|
||||
XCTAssertEqual(tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(publisher.send(42), .none)
|
||||
XCTAssertEqual(tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil),
|
||||
.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil)])
|
||||
tracking.cancel()
|
||||
publisher.cancel()
|
||||
}
|
||||
@@ -304,8 +294,7 @@ final class ReceiveOnTests: XCTestCase {
|
||||
XCTAssertEqual(tracking.history, [.subscription("ReceiveOn")])
|
||||
publisher.send(completion: .finished)
|
||||
XCTAssertEqual(tracking.history, [.subscription("ReceiveOn")])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil),
|
||||
.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.history, [.schedule(options: nil)])
|
||||
tracking.cancel()
|
||||
publisher.cancel()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
//
|
||||
// ReplaceEmptyTests.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Joseph Spadafora on 12/10/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class ReplaceEmptyTests: XCTestCase {
|
||||
func testEmptySubscription() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 15) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty")])
|
||||
}
|
||||
|
||||
func testError() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 42) }
|
||||
)
|
||||
|
||||
helper.publisher.send(completion: .failure(TestingError.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
func testEndWithoutValueReplacesCorrectly() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 42) }
|
||||
)
|
||||
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testNoValueIsReplacedIfEndsWithoutEmpty() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 42) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(3),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSendingValueAndThenError() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .max(1),
|
||||
createSut: { $0.replaceEmpty(with: 42) })
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(8), .max(1))
|
||||
helper.publisher.send(completion: .failure(TestingError.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(8),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
func testFailingBeforeDemanding() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .max(1),
|
||||
createSut: { $0.replaceEmpty(with: 42) })
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty")])
|
||||
|
||||
helper.publisher.send(completion: .failure(TestingError.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.completion(.failure(.oops))])
|
||||
|
||||
helper.downstreamSubscription?.request(.unlimited)
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.completion(.failure(.oops))])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(-1), .none)
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
func testUpstreamCompletesEmptyBeforeDownstreamRequests() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 22) })
|
||||
helper.publisher.send(completion: .finished)
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty")])
|
||||
|
||||
helper.subscription.request(.max(3))
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty")])
|
||||
|
||||
helper.downstreamSubscription?.request(.max(1))
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(22),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
// MARK: - Basic Behavior
|
||||
func testBasicBehavior() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 22) })
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
XCTAssertEqual(helper.publisher.send(4), .none)
|
||||
XCTAssertEqual(helper.publisher.send(6), .none)
|
||||
XCTAssertEqual(helper.publisher.send(7), .none)
|
||||
XCTAssertEqual(helper.publisher.send(8), .none)
|
||||
XCTAssertEqual(helper.publisher.send(9), .none)
|
||||
helper.publisher.send(completion: .finished)
|
||||
XCTAssertEqual(helper.publisher.send(10), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(2),
|
||||
.value(4),
|
||||
.value(6),
|
||||
.value(7),
|
||||
.value(8),
|
||||
.value(9),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testDemand() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(42),
|
||||
receiveValueDemand: .max(4),
|
||||
createSut: { $0.replaceEmpty(with: 832) })
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(0), .max(4))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(2), .max(4))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited)])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(95))
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(5))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5))])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(3), .max(4))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5))])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(121))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121))])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(7), .max(4))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121))])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121)),
|
||||
.cancelled])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(50))
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(42)),
|
||||
.requested(.unlimited),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121)),
|
||||
.cancelled])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(8), .none)
|
||||
}
|
||||
|
||||
func testImmediateCompletion() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 33) })
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
helper.publisher.send(completion: .finished)
|
||||
helper.publisher.send(completion: .finished)
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(33),
|
||||
.completion(.finished)])
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty"),
|
||||
.value(33),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: -7) })
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.unlimited)
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.cancelled])
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceEmpty")])
|
||||
}
|
||||
|
||||
func testReceiveSubscriptionTwice() throws {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 22) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
|
||||
let secondSubscription = CustomSubscription()
|
||||
|
||||
try XCTUnwrap(helper.publisher.subscriber)
|
||||
.receive(subscription: secondSubscription)
|
||||
|
||||
XCTAssertEqual(secondSubscription.history, [.cancelled])
|
||||
|
||||
try XCTUnwrap(helper.publisher.subscriber)
|
||||
.receive(subscription: helper.subscription)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled,
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testReplaceEmptyReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "ReplaceEmpty",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "ReplaceEmpty",
|
||||
{ $0.replaceEmpty(with: 0) })
|
||||
}
|
||||
|
||||
func testCrashesWhenRequestedZeroDemand() {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.replaceEmpty(with: 9) }
|
||||
)
|
||||
|
||||
assertCrashes {
|
||||
helper.downstreamSubscription?.request(.none)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplaceEmptyReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(value: 213,
|
||||
expected: .history([], demand: .none)) {
|
||||
$0.replaceEmpty(with: 742)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplaceEmptyReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(inputType: Int.self,
|
||||
expected: .history([])) {
|
||||
$0.replaceEmpty(with: -14)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplaceEmptyRequestBeforeSubscription() {
|
||||
testRequestBeforeSubscription(inputType: Int.self, shouldCrash: false) {
|
||||
$0.replaceEmpty(with: 19)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplaceEmptyCancelBeforeSubscription() {
|
||||
testCancelBeforeSubscription(inputType: Int.self, shouldCrash: false) {
|
||||
$0.replaceEmpty(with: 1337)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplaceEmptyLifecycle() throws {
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
finishingIsPassedThrough: false,
|
||||
{ $0.replaceEmpty(with: 13) })
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,10 @@ final class SubscribeOnTests: XCTestCase {
|
||||
$0.subscribe(on: ImmediateScheduler.shared)
|
||||
}
|
||||
|
||||
var recursionCounter = 5
|
||||
helper.subscription.onRequest = { _ in
|
||||
if recursionCounter == 0 { return }
|
||||
recursionCounter -= 1
|
||||
helper.downstreamSubscription?.request(.unlimited)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,7 @@ def suffix_variadic(name, index, arity):
|
||||
|
||||
def list_with_suffix_variadic(name, arity):
|
||||
return [suffix_variadic(name, i, arity) for i in range(arity)]
|
||||
|
||||
def indent(input, space_count):
|
||||
padding = space_count * ' '
|
||||
return ''.join(padding + line for line in input.splitlines(True))
|
||||
|
||||
Reference in New Issue
Block a user