Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af0ae86407 |
+12
-12
@@ -63,35 +63,35 @@ jobs:
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
"Execute compatibility tests on iOS 13.6 (Xcode 11.6.0, Swift 5.2.4)":
|
||||
"Execute compatibility tests on iOS 13.3 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
macos:
|
||||
xcode: "11.6.0"
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.2.4"
|
||||
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.6 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.6" \
|
||||
-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.6 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.6" \
|
||||
-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
|
||||
@@ -161,11 +161,11 @@ jobs:
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.2)":
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.1.1)":
|
||||
docker:
|
||||
- image: swift:5.2-bionic
|
||||
- image: swift:5.1.1-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.2"
|
||||
SWIFT_VERSION: "5.1.1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@@ -259,13 +259,13 @@ workflows:
|
||||
- "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.6 (Xcode 11.6.0, Swift 5.2.4)"
|
||||
- "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)"
|
||||
"OpenCombine: execute tests on Linux":
|
||||
jobs:
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.2)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.1.1)"
|
||||
"OpenCombine: run SwiftLint and Danger":
|
||||
jobs:
|
||||
- "Run SwiftLint and Danger"
|
||||
|
||||
@@ -23,7 +23,6 @@ disabled_rules:
|
||||
- trailing_comma
|
||||
- type_body_length
|
||||
- opening_brace
|
||||
- untyped_error_in_catch
|
||||
|
||||
opt_in_rules:
|
||||
- array_init
|
||||
@@ -66,10 +65,6 @@ opt_in_rules:
|
||||
- vertical_whitespace_closing_braces
|
||||
- yoda_condition
|
||||
|
||||
implicit_return:
|
||||
included:
|
||||
- closure
|
||||
|
||||
line_length:
|
||||
warning: 90
|
||||
error: 120
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ GEM
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
json (2.3.1)
|
||||
json (2.2.0)
|
||||
jwt (2.1.0)
|
||||
memoist (0.16.1)
|
||||
mime-types (3.3)
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombine"
|
||||
spec.version = "0.10.1"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "Open source implementation of Apple's Combine framework for processing values over time."
|
||||
|
||||
spec.description = <<-DESC
|
||||
@@ -24,4 +24,4 @@ Pod::Spec.new do |spec|
|
||||
spec.public_header_files = "Sources/COpenCombineHelpers/include/*.h"
|
||||
|
||||
spec.libraries = "c++"
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineDispatch"
|
||||
spec.version = "0.10.1"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "OpenCombine + Dispatch interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
@@ -21,5 +21,5 @@ Pod::Spec.new do |spec|
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineDispatch/**/*.swift"
|
||||
spec.dependency "OpenCombine", '>= 0.9'
|
||||
end
|
||||
spec.dependency "OpenCombine", '>= 0.6'
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineFoundation"
|
||||
spec.version = "0.10.1"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "OpenCombine + OpenCombineFoundation interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
@@ -21,5 +21,5 @@ Pod::Spec.new do |spec|
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineFoundation/**/*.swift"
|
||||
spec.dependency "OpenCombine", '>= 0.9'
|
||||
end
|
||||
spec.dependency "OpenCombine", '~> 0.6'
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
# OpenCombine
|
||||
[](https://circleci.com/gh/OpenCombine/OpenCombine)
|
||||
[](https://codecov.io/gh/OpenCombine/OpenCombine)
|
||||
[](https://circleci.com/gh/broadwaylamb/OpenCombine/tree/master)
|
||||
[](https://codecov.io/gh/broadwaylamb/OpenCombine)
|
||||

|
||||

|
||||

|
||||
@@ -13,9 +13,9 @@ The main goal of this project is to provide a compatible, reliable and efficient
|
||||
The project is in early development.
|
||||
|
||||
### Installation
|
||||
`OpenCombine` contains three public targets: `OpenCombine`, `OpenCombineFoundation` and `OpenCombineDispatch` (the fourth one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
|
||||
`OpenCombine` contains two public targets: `OpenCombine` and `OpenCombineDispatch` (the third one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
|
||||
|
||||
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`. The same applies to Foundation: if you want to use, for instance, `NotificationCenter` or `URLSession` publishers, you'll need to also import `OpenCombineFoundation`
|
||||
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`.
|
||||
|
||||
##### Swift Package Manager
|
||||
###### Swift Package
|
||||
@@ -23,19 +23,17 @@ To add `OpenCombine` to your [SPM](https://swift.org/package-manager/) package,
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.10.1")
|
||||
.package(url: "https://github.com/broadwaylamb/OpenCombine.git", from: "0.7.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"])
|
||||
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine", "OpenCombineDispatch"])
|
||||
]
|
||||
```
|
||||
|
||||
###### Xcode
|
||||
`OpenCombine` can also be added as a SPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
|
||||
|
||||
To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Dependency…**, enter the [repository URL](https://github.com/OpenCombine/OpenCombine.git), choose the latest available version, and activate the checkboxes:
|
||||
To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Dependency…**, enter the [repository URL](https://github.com/broadwaylamb/OpenCombine.git), choose the latest available version, and activate the checkboxes:
|
||||
|
||||
<p align="center">
|
||||
<img alt="Select the OpenCombine and OpenCombineDispatch targets"
|
||||
@@ -46,18 +44,17 @@ To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Depe
|
||||
To add `OpenCombine` to a project using [CocoaPods](https://cocoapods.org/), add `OpenCombine` and `OpenCombineDispatch` to the list of target dependencies in your `Podfile`.
|
||||
|
||||
```ruby
|
||||
pod 'OpenCombine', '~> 0.10.1'
|
||||
pod 'OpenCombineDispatch', '~> 0.10.1'
|
||||
pod 'OpenCombineFoundation', '~> 0.10.1'
|
||||
pod 'OpenCombine', '~> 0.7'
|
||||
pod 'OpenCombineDispatch', '~> 0.7'
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
|
||||
|
||||
Please refer to the [issue #1](https://github.com/OpenCombine/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/OpenCombine/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
|
||||
Please refer to the [issue #1](https://github.com/broadwaylamb/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/broadwaylamb/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
|
||||
|
||||
You can refer to [this repo](https://github.com/OpenCombine/combine-interfaces) to observe Apple's Combine API and documentation changes between different Xcode (beta) versions.
|
||||
You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff851c4c1dbf0a872e26) to observe Apple's Combine API changes between different Xcode (beta) versions, or to [this gist](https://gist.github.com/broadwaylamb/82dc2ce4ffbe06527c2c352b8f10910f) to see the relevant contents of the .swiftinterface file for Combine.
|
||||
|
||||
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
|
||||
|
||||
@@ -67,29 +64,7 @@ $ make test-compatibility
|
||||
|
||||
Or enable the `-DOPENCOMBINE_COMPATIBILITY_TEST` compiler flag in Xcode's build settings. Note that on iOS only the latter will work.
|
||||
|
||||
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/OpenCombine/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
|
||||
|
||||
#### Releasing a new version
|
||||
|
||||
1. Create a new branch from master and call it `release/<major>.<minor>.<patch>`.
|
||||
1. Replace the usages of the old version in `README.md` with the new version (make sure to check the [Swift Package Manager](#swift-package-manager) and [CocoaPods](#cocoapods) sections).
|
||||
1. Bump the version in `OpenCombine.podspec`, `OpenCombineDispatch.podspec` and `OpenCombineFoundation.podspec`. In the latter two you will also need to set the `spec.dependency "OpenCombine"` property to the **previous** version. Why? Because otherwise the `pod lib lint` command that we run on our regular CI will fail when validating the `OpenCombineDispatch` and `OpenCombineFoundation` podspecs, since the dependencies are not yet in the trunk. If we set the dependencies to the previous version (which is already in the trunk), everything will be fine. This is purely to make the CI work. The clients will not experience any issues, since the version is specified as `>=`.
|
||||
1. Create a pull request to master for the release branch and make sure the CI passes.
|
||||
1. Merge the pull request.
|
||||
1. In the GitHub web interface on the [releases](https://github.com/OpenCombine/OpenCombine/releases) page, click the **Draft a new release** button.
|
||||
1. The **Tag version** and **Release title** fields should be filled with the version number.
|
||||
1. The description of the release should be consistent with the previous releases. It is a good practice to divide the description into several sections: additions, bugfixes, known issues etc. Also, be sure to mention the nicknames of the contributors of the new release.
|
||||
1. Publish the release.
|
||||
1. Switch to the master branch and pull the changes.
|
||||
1. Push the release to CocoaPods trunk. For that, execute the following commands:
|
||||
|
||||
```
|
||||
pod trunk push OpenCombine.podspec --verbose --allow-warnings
|
||||
pod trunk push OpenCombineDispatch.podspec --verbose --allow-warnings
|
||||
pod trunk push OpenCombineFoundation.podspec --verbose --allow-warnings
|
||||
```
|
||||
|
||||
Note that you need to be one of the owners of the pod for that.
|
||||
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/broadwaylamb/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
|
||||
|
||||
#### GYB
|
||||
|
||||
@@ -111,16 +86,3 @@ GYB template files have the `.gyb` extension. Run `make gyb` to generate Swift c
|
||||
templates. The generated files are prefixed with `GENERATED-` and are checked into source control. Those
|
||||
files should never be edited directly. Instead, the `.gyb` template should be edited, and after that the files
|
||||
should be regenerated using `make gyb`.
|
||||
|
||||
#### Debugger Support
|
||||
|
||||
The file `opencombine_lldb.py` defines some `lldb` type summaries for easier debugging. These type summaries improve the way `lldb` and Xcode display some OpenCombine values.
|
||||
|
||||
To use `opencombine_lldb.py`, figure out its full path. Let's say the full path is `~/projects/OpenCombine/opencombine_lldb.py`. Then the following statement to your `~/.lldbinit` file:
|
||||
|
||||
command script import ~/projects/OpenCombine/opencombine_lldb.py
|
||||
|
||||
Currently, `opencombine_lldb.py` defines type summaries for these types:
|
||||
|
||||
- `Subscribers.Demand`
|
||||
- That's all for now.
|
||||
|
||||
+326
-456
@@ -263,22 +263,27 @@ extension 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 {
|
||||
/// A publisher that “flattens” nested publishers.
|
||||
///
|
||||
/// Given a publisher that publishes Publishers, the `SwitchToLatest` publisher produces a sequence of events from only the most recent one.
|
||||
/// For example, given the type `Publisher<Publisher<Data, NSError>, Never>`, calling `switchToLatest()` will result in the type `Publisher<Data, NSError>`. The downstream subscriber sees a continuous stream of values even though they may be coming from different upstream publishers.
|
||||
public struct SwitchToLatest<P, Upstream> : Publisher where P : Publisher, P == Upstream.Output, Upstream : Publisher, P.Failure == Upstream.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = A.Output
|
||||
public typealias Output = P.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
public typealias Failure = P.Failure
|
||||
|
||||
public let a: A
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public let b: B
|
||||
|
||||
public init(_ a: A, _ b: B)
|
||||
/// Creates a publisher that “flattens” nested publishers.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives elements.
|
||||
public init(upstream: Upstream)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
@@ -286,369 +291,17 @@ extension Publishers {
|
||||
/// - 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>
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, P.Output == S.Input, Upstream.Failure == S.Failure
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
extension Publisher where Self.Failure == Self.Output.Failure, Self.Output : Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher, delivering an interleaved sequence of elements.
|
||||
/// Flattens the stream of events from multiple upstream publishers to appear as if they were coming from a single stream of events.
|
||||
///
|
||||
/// 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>
|
||||
/// This operator switches the inner publisher as new ones arrive but keeps the outer one constant for downstream subscribers.
|
||||
/// For example, given the type `Publisher<Publisher<Data, NSError>, Never>`, calling `switchToLatest()` will result in the type `Publisher<Data, NSError>`. The downstream subscriber sees a continuous stream of values even though they may be coming from different upstream publishers.
|
||||
public func switchToLatest() -> Publishers.SwitchToLatest<Self.Output, Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
@@ -760,6 +413,219 @@ 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 publishes elements only after a specified time interval elapses between events.
|
||||
public struct Debounce<Upstream, Context> : Publisher where Upstream : Publisher, Context : Scheduler {
|
||||
|
||||
/// 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 amount of time the publisher should wait before publishing an element.
|
||||
public let dueTime: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler on which this publisher delivers elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// Scheduler options that customize this publisher’s delivery of elements.
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream, dueTime: Context.SchedulerTimeType.Stride, scheduler: Context, options: Context.SchedulerOptions?)
|
||||
|
||||
/// 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 {
|
||||
|
||||
/// Publishes elements only after a specified time interval elapses between events.
|
||||
///
|
||||
/// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause.
|
||||
/// - Parameters:
|
||||
/// - dueTime: The time the publisher should wait before publishing an element.
|
||||
/// - scheduler: The scheduler on which this publisher delivers elements
|
||||
/// - options: Scheduler options that customize this publisher’s delivery of elements.
|
||||
/// - Returns: A publisher that publishes events only after a specified time elapses.
|
||||
public func debounce<S>(for dueTime: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.Debounce<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Timeout<Upstream, Context> : Publisher where Upstream : Publisher, Context : Scheduler {
|
||||
|
||||
/// 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 upstream: Upstream
|
||||
|
||||
public let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
public let scheduler: Context
|
||||
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public let customError: (() -> Upstream.Failure)?
|
||||
|
||||
public init(upstream: Upstream, interval: Context.SchedulerTimeType.Stride, scheduler: Context, options: Context.SchedulerOptions?, customError: (() -> Publishers.Timeout<Upstream, Context>.Failure)?)
|
||||
|
||||
/// 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 {
|
||||
|
||||
/// Terminates publishing if the upstream publisher exceeds the specified time interval without producing an element.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The maximum time interval the publisher can go without emitting an element, expressed in the time system of the scheduler.
|
||||
/// - scheduler: The scheduler to deliver events on.
|
||||
/// - options: Scheduler options that customize the delivery of elements.
|
||||
/// - customError: A closure that executes if the publisher times out. The publisher sends the failure returned by this closure to the subscriber as the reason for termination.
|
||||
/// - Returns: A publisher that terminates if the specified interval elapses with no events received from the upstream publisher.
|
||||
public func timeout<S>(_ interval: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil, customError: (() -> Self.Failure)? = nil) -> Publishers.Timeout<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A strategy for filling a buffer.
|
||||
///
|
||||
/// * keepFull: A strategy to fill the buffer at subscription time, and keep it full thereafter.
|
||||
/// * byRequest: A strategy that avoids prefetching and instead performs requests on demand.
|
||||
public enum PrefetchStrategy {
|
||||
|
||||
case keepFull
|
||||
|
||||
case byRequest
|
||||
|
||||
/// 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 == (a: Publishers.PrefetchStrategy, b: Publishers.PrefetchStrategy) -> Bool
|
||||
|
||||
/// The hash value.
|
||||
///
|
||||
/// Hash values are not guaranteed to be equal across different executions of
|
||||
/// your program. Do not save hash values to use during a future execution.
|
||||
///
|
||||
/// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
|
||||
/// conform to `Hashable`, implement the `hash(into:)` requirement instead.
|
||||
public var hashValue: Int { get }
|
||||
|
||||
/// Hashes the essential components of this value by feeding them into the
|
||||
/// given hasher.
|
||||
///
|
||||
/// Implement this method to conform to the `Hashable` protocol. The
|
||||
/// components used for hashing must be the same as the components compared
|
||||
/// in your type's `==` operator implementation. Call `hasher.combine(_:)`
|
||||
/// with each of these components.
|
||||
///
|
||||
/// - Important: Never call `finalize()` on `hasher`. Doing so may become a
|
||||
/// compile-time error in the future.
|
||||
///
|
||||
/// - Parameter hasher: The hasher to use when combining the components
|
||||
/// of this instance.
|
||||
public func hash(into hasher: inout Hasher)
|
||||
}
|
||||
|
||||
/// A strategy for handling exhaustion of a buffer’s capacity.
|
||||
///
|
||||
/// * dropNewest: When full, discard the newly-received element without buffering it.
|
||||
/// * dropOldest: When full, remove the least recently-received element from the buffer.
|
||||
/// * customError: When full, execute the closure to provide a custom error.
|
||||
public enum BufferingStrategy<Failure> where Failure : Error {
|
||||
|
||||
case dropNewest
|
||||
|
||||
case dropOldest
|
||||
|
||||
case customError(() -> Failure)
|
||||
}
|
||||
|
||||
/// A publisher that buffers elements received from an upstream publisher.
|
||||
public struct Buffer<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 elements to store.
|
||||
public let size: Int
|
||||
|
||||
/// The strategy for initially populating the buffer.
|
||||
public let prefetch: Publishers.PrefetchStrategy
|
||||
|
||||
/// The action to take when the buffer becomes full.
|
||||
public let whenFull: Publishers.BufferingStrategy<Upstream.Failure>
|
||||
|
||||
/// Creates a publisher that buffers elements received from an upstream publisher.
|
||||
/// - Parameter upstream: The publisher from which this publisher receives elements.
|
||||
/// - Parameter size: The maximum number of elements to store.
|
||||
/// - Parameter prefetch: The strategy for initially populating the buffer.
|
||||
/// - Parameter whenFull: The action to take when the buffer becomes full.
|
||||
public init(upstream: Upstream, size: Int, prefetch: Publishers.PrefetchStrategy, whenFull: Publishers.BufferingStrategy<Publishers.Buffer<Upstream>.Failure>)
|
||||
|
||||
/// 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 Publishers.PrefetchStrategy : Equatable {
|
||||
}
|
||||
|
||||
extension Publishers.PrefetchStrategy : Hashable {
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Buffers elements received from an upstream publisher.
|
||||
/// - Parameter size: The maximum number of elements to store.
|
||||
/// - Parameter prefetch: The strategy for initially populating the buffer.
|
||||
/// - Parameter whenFull: The action to take when the buffer becomes full.
|
||||
public func buffer(size: Int, prefetch: Publishers.PrefetchStrategy, whenFull: Publishers.BufferingStrategy<Self.Failure>) -> Publishers.Buffer<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the zip function to two upstream publishers.
|
||||
@@ -923,6 +789,100 @@ extension Publisher {
|
||||
public func zip<P, Q, R, T>(_ publisher1: P, _ publisher2: Q, _ publisher3: R, _ transform: @escaping (Self.Output, P.Output, Q.Output, R.Output) -> T) -> Publishers.Map<Publishers.Zip4<Self, P, Q, R>, T> where P : Publisher, Q : Publisher, R : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure, Q.Failure == R.Failure
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher.
|
||||
public struct Catch<Upstream, NewPublisher> : Publisher where Upstream : Publisher, NewPublisher : Publisher, Upstream.Output == NewPublisher.Output {
|
||||
|
||||
/// 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 = NewPublisher.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher.
|
||||
public let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher.
|
||||
public init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher)
|
||||
|
||||
/// 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, NewPublisher.Failure == S.Failure, NewPublisher.Output == S.Input
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher or optionally producing a new error.
|
||||
public struct TryCatch<Upstream, NewPublisher> : Publisher where Upstream : Publisher, NewPublisher : Publisher, Upstream.Output == NewPublisher.Output {
|
||||
|
||||
/// 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 = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher)
|
||||
|
||||
/// 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, NewPublisher.Output == S.Input, S.Failure == Publishers.TryCatch<Upstream, NewPublisher>.Failure
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Handles errors from an upstream publisher by replacing it with another publisher.
|
||||
///
|
||||
/// The following example replaces any error from the upstream publisher and replaces the upstream with a `Just` publisher. This continues the stream by publishing a single value and completing normally.
|
||||
/// ```
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
|
||||
/// if v < 5 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let noErrorPublisher = errorPublisher.catch { _ in
|
||||
/// return Just(100)
|
||||
/// }
|
||||
/// ```
|
||||
/// Backpressure note: This publisher passes through `request` and `cancel` to the upstream. After receiving an error, the publisher sends sends any unfulfilled demand to the new `Publisher`.
|
||||
/// - Parameter handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher.
|
||||
public func `catch`<P>(_ handler: @escaping (Self.Failure) -> P) -> Publishers.Catch<Self, P> where P : Publisher, Self.Output == P.Output
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another publisher or `throw`ing a new error.
|
||||
///
|
||||
/// - Parameter handler: A `throw`ing closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher or if an error is thrown will send the error downstream.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher.
|
||||
public func tryCatch<P>(_ handler: @escaping (Self.Failure) throws -> P) -> Publishers.TryCatch<Self, P> where P : Publisher, Self.Output == P.Output
|
||||
}
|
||||
|
||||
|
||||
extension Publishers.CombineLatest : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
@@ -960,96 +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.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.
|
||||
|
||||
@@ -37,3 +37,127 @@ extension NSObject.KeyValueObservingPublisher : Combine.Publisher {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <cstdlib>
|
||||
#include <system_error>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <os/lock.h>
|
||||
@@ -236,8 +235,4 @@ void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lo
|
||||
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
|
||||
}
|
||||
|
||||
void opencombine_stop_in_debugger(void) {
|
||||
raise(SIGTRAP);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#define COPENCOMBINEHELPERS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
|
||||
#if __has_attribute(swift_name)
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
|
||||
@@ -16,6 +17,12 @@
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name)
|
||||
#endif
|
||||
|
||||
#if __has_attribute(always_inline)
|
||||
# define OPENCOMBINE_ALWAYS_INLINE __attribute__((always_inline))
|
||||
#else
|
||||
# define OPENCOMBINE_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -70,7 +77,12 @@ void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lo
|
||||
|
||||
#pragma mark - Breakpoint
|
||||
|
||||
void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
|
||||
OPENCOMBINE_ALWAYS_INLINE
|
||||
inline void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
|
||||
|
||||
void opencombine_stop_in_debugger(void) {
|
||||
raise(SIGTRAP);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
||||
@@ -57,7 +57,7 @@ extension AnyCancellable {
|
||||
|
||||
/// Stores this AnyCancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - set: The set to store this AnyCancellable.
|
||||
/// - collection: The set to store this AnyCancellable.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
set.insert(self)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ extension Publisher {
|
||||
///
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher` to
|
||||
/// the downstream subscriber, rather than this publisher’s actual type.
|
||||
@inlinable
|
||||
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
|
||||
return .init(self)
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
|
||||
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
|
||||
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
|
||||
} else if let description = subscriber as? CustomStringConvertible {
|
||||
playgroundDescriptionThunk = { description.description }
|
||||
} else if let desccription = subscriber as? CustomStringConvertible {
|
||||
playgroundDescriptionThunk = { desccription.description }
|
||||
} else {
|
||||
let fixedDescription = String(describing: type(of: subscriber))
|
||||
playgroundDescriptionThunk = { fixedDescription }
|
||||
|
||||
@@ -28,7 +28,7 @@ extension Cancellable {
|
||||
|
||||
/// Stores this Cancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - set: The set to store this Cancellable.
|
||||
/// - collection: The set to store this Cancellable.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
AnyCancellable(self).store(in: &set)
|
||||
}
|
||||
|
||||
@@ -9,29 +9,26 @@
|
||||
/// changes.
|
||||
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var active = true
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
private var completion: Subscribers.Completion<Failure>?
|
||||
private var _value: Output
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var currentValue: Output
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
private var upstreamSubscriptions: [Subscription] = []
|
||||
internal var hasAnyDownstreamDemand = false
|
||||
|
||||
/// The value wrapped by this subject, published as a new element whenever it changes.
|
||||
public var value: Output {
|
||||
get {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return currentValue
|
||||
return _value
|
||||
}
|
||||
set {
|
||||
lock.lock()
|
||||
currentValue = newValue
|
||||
sendValueAndConsumeLock(newValue)
|
||||
send(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,213 +36,122 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
///
|
||||
/// - Parameter value: The initial value to publish.
|
||||
public init(_ value: Output) {
|
||||
self.currentValue = value
|
||||
self._value = value
|
||||
}
|
||||
|
||||
deinit {
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.cancel()
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
lock.deallocate()
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
lock.lock()
|
||||
upstreamSubscriptions.append(subscription)
|
||||
lock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Output == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
lock.lock()
|
||||
if active {
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
} else {
|
||||
let completion = self.completion!
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
_lock.do {
|
||||
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
lock.lock()
|
||||
sendValueAndConsumeLock(input)
|
||||
}
|
||||
|
||||
private func sendValueAndConsumeLock(_ newValue: Output) {
|
||||
#if DEBUG
|
||||
lock.assertOwner()
|
||||
#endif
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
currentValue = newValue
|
||||
let downstreams = self.downstreams
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.offer(newValue)
|
||||
_lock.do {
|
||||
_value = input
|
||||
for subscription in _subscriptions where !subscription.isCompleted {
|
||||
if subscription._demand > 0 {
|
||||
subscription._offer(input)
|
||||
subscription._demand -= 1
|
||||
} else {
|
||||
subscription._delivered = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
_completion = completion
|
||||
_lock.do {
|
||||
for subscriber in _subscriptions {
|
||||
subscriber._receive(completion: completion)
|
||||
}
|
||||
}
|
||||
active = false
|
||||
self.completion = completion
|
||||
let downstreams = self.downstreams
|
||||
self.downstreams.removeAll()
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.finish(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentValueSubject {
|
||||
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
fileprivate class Conduit: Subscription {
|
||||
|
||||
fileprivate var parent: CurrentValueSubject?
|
||||
fileprivate var _parent: CurrentValueSubject?
|
||||
|
||||
fileprivate var downstream: Downstream?
|
||||
fileprivate var _downstream: AnySubscriber<Output, Failure>?
|
||||
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
/// Whethere we satisfied the demand
|
||||
fileprivate var _delivered = false
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
var isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
|
||||
private var deliveredCurrentValue = false
|
||||
fileprivate func _offer(_ value: Output) {
|
||||
let newDemand = _downstream?.receive(value) ?? .none
|
||||
_demand += newDemand
|
||||
_delivered = true
|
||||
}
|
||||
|
||||
fileprivate init(parent: CurrentValueSubject,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
downstream: AnySubscriber<Output, Failure>) {
|
||||
_parent = parent
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
lock.lock()
|
||||
guard demand > 0, let downstream = self.downstream else {
|
||||
deliveredCurrentValue = false
|
||||
lock.unlock()
|
||||
return
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
}
|
||||
demand -= 1
|
||||
deliveredCurrentValue = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(output)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
precondition(demand > 0)
|
||||
_parent?._lock.do {
|
||||
if !_delivered, let value = _parent?.value {
|
||||
_offer(value)
|
||||
_demand += demand
|
||||
_demand -= 1
|
||||
} else {
|
||||
_demand = demand
|
||||
}
|
||||
_parent?.hasAnyDownstreamDemand = true
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if deliveredCurrentValue {
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Hasn't yet delivered the current value
|
||||
|
||||
self.demand += demand
|
||||
deliveredCurrentValue = true
|
||||
if let currentValue = self.parent?.value {
|
||||
self.demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(currentValue)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
self.demand += newDemand
|
||||
}
|
||||
lock.unlock()
|
||||
func cancel() {
|
||||
_parent = nil
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
var description: String { return "CurrentValueSubject" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("demand", demand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentValueSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "CurrentValueSubject" }
|
||||
}
|
||||
|
||||
@@ -6,197 +6,113 @@
|
||||
//
|
||||
|
||||
/// A publisher that eventually produces one value and then finishes or fails.
|
||||
public final class Future<Output, Failure: Error>: Publisher {
|
||||
public final class Future<Output, Failure>: Publisher where Failure: Error {
|
||||
|
||||
public typealias Promise = (Result<Output, Failure>) -> Void
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
private var result: Result<Output, Failure>?
|
||||
|
||||
public init(
|
||||
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
|
||||
) {
|
||||
attemptToFulfill(self.promise)
|
||||
attemptToFulfill { result in
|
||||
self._lock.do {
|
||||
guard self.result == nil else { return }
|
||||
self.result = result
|
||||
self._publish(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
private func promise(_ result: Result<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard self.result == nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.result = result
|
||||
let downstreams = self.downstreams
|
||||
self.downstreams.removeAll()
|
||||
lock.unlock()
|
||||
switch result {
|
||||
case .success(let output):
|
||||
downstreams.forEach { $0.offer(output) }
|
||||
case .failure(let error):
|
||||
downstreams.forEach { $0.finish(completion: .failure(error)) }
|
||||
/// 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<Downstream: Subscriber>(
|
||||
subscriber: Downstream
|
||||
) where Output == Downstream.Input, Failure == Downstream.Failure {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
private func _acknowledgeDownstreamDemand() {
|
||||
_lock.do {
|
||||
guard let result = result else { return }
|
||||
_publish(result)
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
lock.lock()
|
||||
if let result = self.result {
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
conduit.fulfill(result)
|
||||
} else {
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
}
|
||||
}
|
||||
private func _publish(_ result: Result<Output, Failure>) {
|
||||
for subscription in self._subscriptions where !subscription._isCompleted {
|
||||
switch result {
|
||||
case let .success(output) where subscription._demand > 0:
|
||||
subscription._demand -= 1
|
||||
subscription._demand += subscription._downstream?.receive(output) ?? .none
|
||||
subscription._receive(completion: .finished)
|
||||
case let .failure(error):
|
||||
subscription._receive(completion: .failure(error))
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
// nothing to do if no demand
|
||||
default: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Future {
|
||||
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
fileprivate final class Conduit: Subscription {
|
||||
|
||||
fileprivate var parent: Future?
|
||||
fileprivate var _parent: Future<Output, Failure>?
|
||||
|
||||
fileprivate var downstream: Downstream?
|
||||
fileprivate var _downstream: AnySubscriber<Output, Failure>?
|
||||
|
||||
fileprivate var hasAnyDemand = false
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(parent: Future, downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
fileprivate var _isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
fileprivate init(parent: Future<Output, Failure>,
|
||||
downstream: AnySubscriber<Output, Failure>) {
|
||||
_parent = parent
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
fileprivate func fulfill(_ result: Result<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let parent = self.parent
|
||||
if case .success = result, !hasAnyDemand {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
switch result {
|
||||
case .success(let output):
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
downstreamLock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
fulfill(.success(output))
|
||||
}
|
||||
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
assertionFailure("unreachable")
|
||||
case .failure(let error):
|
||||
fulfill(.failure(error))
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !_isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream, let parent = self.parent else {
|
||||
lock.unlock()
|
||||
return
|
||||
_parent?._lock.do {
|
||||
_demand += demand
|
||||
}
|
||||
hasAnyDemand = true
|
||||
|
||||
parent.lock.lock()
|
||||
guard let result = parent.result else {
|
||||
parent.lock.unlock()
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
parent.lock.unlock()
|
||||
self.downstream = nil
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
switch result {
|
||||
case .success(let output):
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
// This branch is not reachable under normal circumstances,
|
||||
// but may be reachable in case of a race condition.
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
downstreamLock.unlock()
|
||||
parent.disassociate(self)
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
fileprivate func cancel() {
|
||||
_parent = nil
|
||||
}
|
||||
|
||||
var description: String { return "Future" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("hasAnyDemand", hasAnyDemand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Future.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "Future" }
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
//
|
||||
// ConduitBase.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.06.2020.
|
||||
//
|
||||
|
||||
internal class ConduitBase<Output, Failure: Error>: Subscription {
|
||||
|
||||
internal init() {}
|
||||
|
||||
internal func offer(_ output: Output) {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal func finish(completion: Subscribers.Completion<Failure>) {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
abstractMethod()
|
||||
}
|
||||
}
|
||||
|
||||
extension ConduitBase: Equatable {
|
||||
internal static func == (lhs: ConduitBase<Output, Failure>,
|
||||
rhs: ConduitBase<Output, Failure>) -> Bool {
|
||||
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConduitBase: Hashable {
|
||||
internal func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
//
|
||||
// ConduitList.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.06.2020.
|
||||
//
|
||||
|
||||
internal enum ConduitList<Output, Failure: Error> {
|
||||
case empty
|
||||
case single(ConduitBase<Output, Failure>)
|
||||
case many(Set<ConduitBase<Output, Failure>>)
|
||||
}
|
||||
|
||||
extension ConduitList {
|
||||
internal mutating func insert(_ conduit: ConduitBase<Output, Failure>) {
|
||||
switch self {
|
||||
case .empty:
|
||||
self = .single(conduit)
|
||||
case .single(conduit):
|
||||
break // This element already exists.
|
||||
case .single(let existingConduit):
|
||||
self = .many([existingConduit, conduit])
|
||||
case .many(var set):
|
||||
set.insert(conduit)
|
||||
self = .many(set)
|
||||
}
|
||||
}
|
||||
|
||||
internal func forEach(
|
||||
_ body: (ConduitBase<Output, Failure>) throws -> Void
|
||||
) rethrows {
|
||||
switch self {
|
||||
case .empty:
|
||||
break
|
||||
case .single(let conduit):
|
||||
try body(conduit)
|
||||
case .many(let set):
|
||||
try set.forEach(body)
|
||||
}
|
||||
}
|
||||
|
||||
internal mutating func remove(_ conduit: ConduitBase<Output, Failure>) {
|
||||
switch self {
|
||||
case .single(conduit):
|
||||
self = .empty
|
||||
case .empty, .single:
|
||||
break
|
||||
case .many(var set):
|
||||
set.remove(conduit)
|
||||
self = .many(set)
|
||||
}
|
||||
}
|
||||
|
||||
internal mutating func removeAll() {
|
||||
self = .empty
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
///
|
||||
/// Filter-like operators send an instance of their `Inner` class that is subclass
|
||||
/// of this class to the upstream publisher (as subscriber) and
|
||||
/// to the downstream subscriber (as subscription).
|
||||
/// to the downstream subcriber (as subscription).
|
||||
///
|
||||
/// Filter-like operators include `Publishers.Filter`,
|
||||
/// `Publishers.RemoveDuplicates`, `Publishers.PrefixWhile` and more.
|
||||
|
||||
@@ -11,3 +11,13 @@ import COpenCombineHelpers
|
||||
|
||||
internal typealias UnfairLock = __UnfairLock
|
||||
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
|
||||
|
||||
extension UnfairRecursiveLock {
|
||||
|
||||
@inlinable
|
||||
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
|
||||
lock()
|
||||
defer { unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
///
|
||||
/// Reduce-like operators send an instance of their `Inner` class that is subclass
|
||||
/// of this class to the upstream publisher (as subscriber) and
|
||||
/// to the downstream subscriber (as subscription).
|
||||
/// to the downstream subcriber (as subsription).
|
||||
///
|
||||
/// Reduce-like operators include `Publishers.Reduce`, `Publishers.TryReduce`,
|
||||
/// `Publishers.Count`, `Publishers.FirstWhere`, `Publishers.AllSatisfy` and more.
|
||||
|
||||
@@ -40,21 +40,23 @@ internal final class SubjectSubscriber<Downstream: Subject>
|
||||
|
||||
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard let subject = downstreamSubject, upstreamSubscription != nil else {
|
||||
guard let downstreamSubject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
guard upstreamSubscription != nil else { APIViolationValueBeforeSubscription() }
|
||||
lock.unlock()
|
||||
subject.send(input)
|
||||
downstreamSubject.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
lock.lock()
|
||||
guard let subject = downstreamSubject, upstreamSubscription != nil else {
|
||||
guard let subject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
guard upstreamSubscription != nil else { APIViolationUnexpectedCompletion() }
|
||||
lock.unlock()
|
||||
subject.send(completion: completion)
|
||||
downstreamSubject = nil
|
||||
@@ -85,7 +87,11 @@ internal final class SubjectSubscriber<Downstream: Subject>
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
guard !isCancelled, let subscription = upstreamSubscription else {
|
||||
if isCancelled {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
guard let subscription = upstreamSubscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,14 +10,3 @@ internal enum SubscriptionStatus {
|
||||
case subscribed(Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
extension SubscriptionStatus {
|
||||
internal var isAwaitingSubscription: Bool {
|
||||
switch self {
|
||||
case .awaitingSubscription:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
///
|
||||
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
|
||||
/// specific values on-demand during tests.
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var active = true
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var completion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
@@ -26,197 +25,112 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
public init() {}
|
||||
|
||||
deinit {
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.cancel()
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
lock.deallocate()
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
lock.lock()
|
||||
upstreamSubscriptions.append(subscription)
|
||||
let hasAnyDownstreamDemand = self.hasAnyDownstreamDemand
|
||||
lock.unlock()
|
||||
if hasAnyDownstreamDemand {
|
||||
subscription.request(.unlimited)
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
if hasAnyDownstreamDemand {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
lock.lock()
|
||||
if active {
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
} else {
|
||||
let completion = self.completion!
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
_lock.do {
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let downstreams = self.downstreams
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.offer(input)
|
||||
_lock.do {
|
||||
for subscription in _subscriptions
|
||||
where !subscription._isCompleted && subscription._demand > 0
|
||||
{
|
||||
let newDemand = subscription._downstream?.receive(input) ?? .none
|
||||
subscription._demand += newDemand
|
||||
subscription._demand -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
active = false
|
||||
self.completion = completion
|
||||
let downstreams = self.downstreams
|
||||
self.downstreams.removeAll()
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.finish(completion: completion)
|
||||
_lock.do {
|
||||
_completion = completion
|
||||
for subscriber in _subscriptions {
|
||||
subscriber._receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func acknowledgeDownstreamDemand() {
|
||||
lock.lock()
|
||||
if hasAnyDownstreamDemand {
|
||||
lock.unlock()
|
||||
return
|
||||
private func _acknowledgeDownstreamDemand() {
|
||||
_lock.do {
|
||||
guard !hasAnyDownstreamDemand else { return }
|
||||
hasAnyDownstreamDemand = true
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
hasAnyDownstreamDemand = true
|
||||
let upstreamSubscriptions = self.upstreamSubscriptions
|
||||
lock.unlock()
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject {
|
||||
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
fileprivate final class Conduit: Subscription {
|
||||
|
||||
fileprivate var parent: PassthroughSubject?
|
||||
fileprivate var _parent: PassthroughSubject?
|
||||
|
||||
fileprivate var downstream: Downstream?
|
||||
fileprivate var _downstream: AnySubscriber<Output, Failure>?
|
||||
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
fileprivate var _isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
|
||||
fileprivate init(parent: PassthroughSubject,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
downstream: AnySubscriber<Output, Failure>) {
|
||||
_parent = parent
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
lock.lock()
|
||||
guard demand > 0, let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !_isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
}
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(output)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
_parent?._lock.do {
|
||||
_demand += demand
|
||||
}
|
||||
self.demand += demand
|
||||
let parent = self.parent
|
||||
lock.unlock()
|
||||
parent?.acknowledgeDownstreamDemand()
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
fileprivate func cancel() {
|
||||
_parent = nil
|
||||
}
|
||||
|
||||
var description: String { return "PassthroughSubject" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("demand", demand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "PassthroughSubject" }
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
@propertyWrapper
|
||||
public struct Published<Value> {
|
||||
|
||||
@inlinable // trivially forwarding
|
||||
/// Initialize the storage of the `Published` property as well as the corresponding
|
||||
/// `Publisher`.
|
||||
public init(initialValue: Value) {
|
||||
self.init(wrappedValue: initialValue)
|
||||
}
|
||||
|
||||
@@ -1,620 +0,0 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.Catch.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Handles errors from an upstream publisher by replacing it with another publisher.
|
||||
///
|
||||
/// The following example replaces any error from the upstream publisher and replaces
|
||||
/// the upstream with a `Just` publisher. This continues the stream by publishing
|
||||
/// a single value and completing normally.
|
||||
/// ```
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
|
||||
/// if v < 5 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let noErrorPublisher = errorPublisher.catch { _ in
|
||||
/// return Just(100)
|
||||
/// }
|
||||
/// ```
|
||||
/// Backpressure note: This publisher passes through `request` and `cancel` to
|
||||
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
|
||||
/// demand to the new `Publisher`.
|
||||
///
|
||||
/// - Parameter handler: A closure that accepts the upstream failure as input and
|
||||
/// returns a publisher to replace the upstream publisher.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
public func `catch`<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) -> NewPublisher
|
||||
) -> Publishers.Catch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another
|
||||
/// publisher or `throw`ing a new error.
|
||||
///
|
||||
/// - Parameter handler: A `throw`ing closure that accepts the upstream failure as
|
||||
/// input and returns a publisher to replace the upstream publisher or if an error
|
||||
/// is thrown will send the error downstream.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
public func tryCatch<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) throws -> NewPublisher
|
||||
) -> Publishers.TryCatch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher.
|
||||
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = NewPublisher.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and returns a publisher
|
||||
/// to replace the upstream publisher.
|
||||
public let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and returns
|
||||
/// a publisher to replace the upstream publisher.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher or optionally producing a new error.
|
||||
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Catch {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output,
|
||||
Downstream.Failure == NewPublisher.Failure
|
||||
{
|
||||
struct UncaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePre(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePre(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
return inner.receivePre(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
struct CaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NewPublisher.Output
|
||||
|
||||
typealias Failure = NewPublisher.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePost(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePost(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.receivePost(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case pendingPre
|
||||
case pre(Subscription)
|
||||
case pendingPost
|
||||
case post(Subscription)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var state = State.pendingPre
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
init(downstream: Downstream,
|
||||
handler: @escaping (Upstream.Failure) -> NewPublisher) {
|
||||
self.downstream = downstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receivePre(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPre = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .pre(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .pendingPre, .pendingPost, .post, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure(let error):
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .pendingPost
|
||||
lock.unlock()
|
||||
handler(error).subscribe(CaughtS(inner: self))
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
case .pendingPre, .post, .pendingPost:
|
||||
completionBeforeSubscription()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPost = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .post(subscription)
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
|
||||
lock.lock()
|
||||
guard case .post = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pendingPre:
|
||||
// The client is only able to call the `request` method after we've sent
|
||||
// `self` downstream. We only do it in the `receivePre(subscription:)`
|
||||
// method, after setting `state` to `pre`.
|
||||
// After that `state` never becomes `pendingPre`.
|
||||
requestBeforeSubscription()
|
||||
case let .pre(subscription):
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .pendingPost:
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
case let .post(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .pre(subscription), let .post(subscription):
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
case .pendingPre, .pendingPost, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "Catch" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCatch {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output,
|
||||
Downstream.Failure == Error
|
||||
{
|
||||
struct UncaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePre(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePre(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
return inner.receivePre(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
struct CaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NewPublisher.Output
|
||||
|
||||
typealias Failure = NewPublisher.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePost(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePost(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.receivePost(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case pendingPre
|
||||
case pre(Subscription)
|
||||
case pendingPost
|
||||
case post(Subscription)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var state = State.pendingPre
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
init(downstream: Downstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.downstream = downstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receivePre(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPre = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .pre(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .pendingPre, .pendingPost, .post, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure(let error):
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .pendingPost
|
||||
lock.unlock()
|
||||
do {
|
||||
try handler(error).subscribe(CaughtS(inner: self))
|
||||
} catch let anotherError {
|
||||
lock.lock()
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .failure(anotherError))
|
||||
}
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
case .pendingPre, .post, .pendingPost:
|
||||
completionBeforeSubscription()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPost = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .post(subscription)
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
|
||||
lock.lock()
|
||||
guard case .post = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pendingPre:
|
||||
// The client is only able to call the `request` method after we've sent
|
||||
// `self` downstream. We only do it in the `receivePre(subscription:)`
|
||||
// method, after setting `state` to `pre`.
|
||||
// After that `state` never becomes `pendingPre`.
|
||||
requestBeforeSubscription()
|
||||
case let .pre(subscription):
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .pendingPost:
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
case let .post(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .pre(subscription), let .post(subscription):
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
case .pendingPre, .pendingPost, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "TryCatch" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
private func completionBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: received completion but do not have subscription",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
private func requestBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: request before subscription sent",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -235,7 +235,7 @@ extension Optional.OCombine.Publisher {
|
||||
in range: RangeExpression
|
||||
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
|
||||
let range = range.relative(to: 0 ..< Int.max)
|
||||
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
|
||||
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
|
||||
|
||||
// I don't know why, but Combine has this precondition
|
||||
precondition(range.upperBound < .max - 1)
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
//
|
||||
// Publishers.Buffer.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.01.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Buffers elements received from an upstream publisher.
|
||||
/// - Parameter size: The maximum number of elements to store.
|
||||
/// - Parameter prefetch: The strategy for initially populating the buffer.
|
||||
/// - Parameter whenFull: The action to take when the buffer becomes full.
|
||||
public func buffer(
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Failure>
|
||||
) -> Publishers.Buffer<Self> {
|
||||
return .init(upstream: self,
|
||||
size: size,
|
||||
prefetch: prefetch,
|
||||
whenFull: whenFull)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A strategy for filling a buffer.
|
||||
///
|
||||
/// * keepFull: A strategy to fill the buffer at subscription time, and keep it full
|
||||
/// thereafter.
|
||||
/// * byRequest: A strategy that avoids prefetching and instead performs requests
|
||||
/// on demand.
|
||||
public enum PrefetchStrategy {
|
||||
|
||||
/// A strategy to fill the buffer at subscription time, and keep it full
|
||||
/// thereafter.
|
||||
case keepFull
|
||||
|
||||
/// A strategy that avoids prefetching and instead performs requests
|
||||
/// on demand.
|
||||
case byRequest
|
||||
}
|
||||
|
||||
/// A strategy for handling exhaustion of a buffer’s capacity.
|
||||
///
|
||||
/// * dropNewest: When full, discard the newly-received element without buffering it.
|
||||
/// * dropOldest: When full, remove the least recently-received element from the
|
||||
/// buffer.
|
||||
/// * customError: When full, execute the closure to provide a custom error.
|
||||
public enum BufferingStrategy<Failure: Error> {
|
||||
|
||||
/// When full, discard the newly-received element without buffering it.
|
||||
case dropNewest
|
||||
|
||||
/// When full, remove the least recently-received element from the buffer.
|
||||
case dropOldest
|
||||
|
||||
/// When full, execute the closure to provide a custom error.
|
||||
case customError(() -> Failure)
|
||||
}
|
||||
|
||||
/// A publisher that buffers elements received from an upstream publisher.
|
||||
public struct Buffer<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 elements to store.
|
||||
public let size: Int
|
||||
|
||||
/// The strategy for initially populating the buffer.
|
||||
public let prefetch: Publishers.PrefetchStrategy
|
||||
|
||||
/// The action to take when the buffer becomes full.
|
||||
public let whenFull: Publishers.BufferingStrategy<Failure>
|
||||
|
||||
/// Creates a publisher that buffers elements received from an upstream publisher.
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
/// - Parameter size: The maximum number of elements to store.
|
||||
/// - Parameter prefetch: The strategy for initially populating the buffer.
|
||||
/// - Parameter whenFull: The action to take when the buffer becomes full.
|
||||
public init(upstream: Upstream,
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Failure>) {
|
||||
self.upstream = upstream
|
||||
self.size = size
|
||||
self.prefetch = prefetch
|
||||
self.whenFull = whenFull
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, buffer: self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.PrefetchStrategy: Equatable {}
|
||||
|
||||
extension Publishers.PrefetchStrategy: Hashable {}
|
||||
|
||||
extension Publishers.Buffer {
|
||||
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 enum State {
|
||||
case ready(Publishers.Buffer<Upstream>, Downstream)
|
||||
case subscribed(Publishers.Buffer<Upstream>, Downstream, Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var recursion = false
|
||||
|
||||
private var state: State
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
// TODO: Use a deque here?
|
||||
// Need to measure performance with large buffers and `dropOldest` strategy.
|
||||
private var values = [Input]()
|
||||
|
||||
private var upstreamFailed = false
|
||||
|
||||
private var terminal: Subscribers.Completion<Failure>?
|
||||
|
||||
init(downstream: Downstream, buffer: Publishers.Buffer<Upstream>) {
|
||||
state = .ready(buffer, downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(buffer, downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(buffer, downstream, subscription)
|
||||
lock.unlock()
|
||||
|
||||
let upstreamDemand: Subscribers.Demand
|
||||
switch buffer.prefetch {
|
||||
case .keepFull:
|
||||
upstreamDemand = .max(buffer.size)
|
||||
case .byRequest:
|
||||
upstreamDemand = .unlimited
|
||||
}
|
||||
subscription.request(upstreamDemand)
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(buffer, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
switch terminal {
|
||||
case nil, .finished?:
|
||||
if values.count >= buffer.size {
|
||||
switch buffer.whenFull {
|
||||
case .dropNewest:
|
||||
lock.unlock()
|
||||
return drain()
|
||||
case .dropOldest:
|
||||
values.removeFirst()
|
||||
case let .customError(makeError):
|
||||
terminal = .failure(makeError())
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
values.append(input)
|
||||
lock.unlock()
|
||||
return drain()
|
||||
case .failure?:
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state, terminal == nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
terminal = completion
|
||||
lock.unlock()
|
||||
_ = drain()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamDemand += demand
|
||||
let recursion = self.recursion
|
||||
lock.unlock()
|
||||
if recursion {
|
||||
return
|
||||
}
|
||||
|
||||
// Request the number of items just enough to fill the buffer.
|
||||
subscription.request(drain() + demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
values = []
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
private func drain() -> Subscribers.Demand {
|
||||
var upstreamDemand = Subscribers.Demand.none
|
||||
lock.lock()
|
||||
while true {
|
||||
guard case let .subscribed(buffer, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return upstreamDemand
|
||||
}
|
||||
|
||||
if downstreamDemand > 0 {
|
||||
if values.isEmpty {
|
||||
if let completion = terminal {
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
return upstreamDemand
|
||||
}
|
||||
} else {
|
||||
if let completion = terminal, case .failure = completion {
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
return upstreamDemand
|
||||
}
|
||||
|
||||
let poppedValues = lockedPop(downstreamDemand)
|
||||
assert(poppedValues.count > 0,
|
||||
"""
|
||||
We check that the buffer is not empty and downstreamDemand is \
|
||||
nonzero, how can this be triggered?
|
||||
""")
|
||||
|
||||
// This should not crash because `lockedPop(_:)` returns at most
|
||||
// `downstreamDemand` items.
|
||||
downstreamDemand -= poppedValues.count
|
||||
|
||||
recursion = true
|
||||
lock.unlock()
|
||||
|
||||
var newDownstreamDemand = Subscribers.Demand.none
|
||||
var additionalUpstreamDemand = 0
|
||||
|
||||
for value in poppedValues {
|
||||
newDownstreamDemand += downstream.receive(value)
|
||||
additionalUpstreamDemand += 1
|
||||
}
|
||||
|
||||
if buffer.prefetch == .keepFull {
|
||||
upstreamDemand += additionalUpstreamDemand
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
recursion = false
|
||||
downstreamDemand += newDownstreamDemand
|
||||
}
|
||||
}
|
||||
|
||||
private func lockedPop(_ demand: Subscribers.Demand) -> [Input] {
|
||||
assert(demand > 0)
|
||||
guard let max = demand.max else {
|
||||
let poppedValues = self.values
|
||||
self.values = []
|
||||
return poppedValues
|
||||
}
|
||||
|
||||
let poppedValues = Array(values.prefix(max))
|
||||
values.removeFirst(poppedValues.count)
|
||||
return poppedValues
|
||||
}
|
||||
|
||||
var description: String { return "Buffer" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("values", values),
|
||||
("state", state),
|
||||
("downstreamDemand", downstreamDemand),
|
||||
("terminal", terminal as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.Catch.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
%{
|
||||
instantiations = ['Catch', 'TryCatch']
|
||||
}%
|
||||
extension Publisher {
|
||||
|
||||
/// Handles errors from an upstream publisher by replacing it with another publisher.
|
||||
///
|
||||
/// The following example replaces any error from the upstream publisher and replaces
|
||||
/// the upstream with a `Just` publisher. This continues the stream by publishing
|
||||
/// a single value and completing normally.
|
||||
/// ```
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
|
||||
/// if v < 5 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let noErrorPublisher = errorPublisher.catch { _ in
|
||||
/// return Just(100)
|
||||
/// }
|
||||
/// ```
|
||||
/// Backpressure note: This publisher passes through `request` and `cancel` to
|
||||
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
|
||||
/// demand to the new `Publisher`.
|
||||
///
|
||||
/// - Parameter handler: A closure that accepts the upstream failure as input and
|
||||
/// returns a publisher to replace the upstream publisher.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
public func `catch`<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) -> NewPublisher
|
||||
) -> Publishers.Catch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another
|
||||
/// publisher or `throw`ing a new error.
|
||||
///
|
||||
/// - Parameter handler: A `throw`ing closure that accepts the upstream failure as
|
||||
/// input and returns a publisher to replace the upstream publisher or if an error
|
||||
/// is thrown will send the error downstream.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
public func tryCatch<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) throws -> NewPublisher
|
||||
) -> Publishers.TryCatch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher.
|
||||
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = NewPublisher.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and returns a publisher
|
||||
/// to replace the upstream publisher.
|
||||
public let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and returns
|
||||
/// a publisher to replace the upstream publisher.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher or optionally producing a new error.
|
||||
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
}
|
||||
% for instantiation in instantiations:
|
||||
% throws_modifier = ' throws' if instantiation == 'TryCatch' else ''
|
||||
|
||||
extension Publishers.${instantiation} {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output,
|
||||
% if instantiation == 'Catch':
|
||||
Downstream.Failure == NewPublisher.Failure
|
||||
% else:
|
||||
Downstream.Failure == Error
|
||||
% end
|
||||
{
|
||||
struct UncaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePre(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePre(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
return inner.receivePre(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
struct CaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NewPublisher.Output
|
||||
|
||||
typealias Failure = NewPublisher.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePost(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePost(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.receivePost(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case pendingPre
|
||||
case pre(Subscription)
|
||||
case pendingPost
|
||||
case post(Subscription)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var state = State.pendingPre
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let handler: (Upstream.Failure)${throws_modifier} -> NewPublisher
|
||||
|
||||
init(downstream: Downstream,
|
||||
handler: @escaping (Upstream.Failure)${throws_modifier} -> NewPublisher) {
|
||||
self.downstream = downstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receivePre(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPre = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .pre(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .pendingPre, .pendingPost, .post, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure(let error):
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .pendingPost
|
||||
lock.unlock()
|
||||
% if instantiation == 'Catch':
|
||||
handler(error).subscribe(CaughtS(inner: self))
|
||||
% else:
|
||||
do {
|
||||
try handler(error).subscribe(CaughtS(inner: self))
|
||||
} catch let anotherError {
|
||||
lock.lock()
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .failure(anotherError))
|
||||
}
|
||||
% end
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
case .pendingPre, .post, .pendingPost:
|
||||
completionBeforeSubscription()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPost = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .post(subscription)
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
|
||||
lock.lock()
|
||||
guard case .post = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
% if instantiation == 'Catch':
|
||||
downstream.receive(completion: completion)
|
||||
% else:
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
% end
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pendingPre:
|
||||
// The client is only able to call the `request` method after we've sent
|
||||
// `self` downstream. We only do it in the `receivePre(subscription:)`
|
||||
// method, after setting `state` to `pre`.
|
||||
// After that `state` never becomes `pendingPre`.
|
||||
requestBeforeSubscription()
|
||||
case let .pre(subscription):
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .pendingPost:
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
case let .post(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .pre(subscription), let .post(subscription):
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
case .pendingPre, .pendingPost, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "${instantiation}" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
% end
|
||||
|
||||
private func completionBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: received completion but do not have subscription",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
private func requestBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: request before subscription sent",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ extension Publisher {
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that emits all of one publisher’s elements before those from another
|
||||
/// A publisher that emits all of one publisher’s elements before those from anothe
|
||||
/// publisher.
|
||||
public struct Concatenate<Prefix: Publisher, Suffix: Publisher>: Publisher
|
||||
where Prefix.Failure == Suffix.Failure, Prefix.Output == Suffix.Output
|
||||
@@ -110,8 +110,8 @@ extension Publishers {
|
||||
where Suffix.Failure == Downstream.Failure, Suffix.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, suffix: suffix)
|
||||
subscriber.receive(subscription: inner)
|
||||
prefix.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
//
|
||||
// Publishers.Debounce.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes elements only after a specified time interval elapses between events.
|
||||
///
|
||||
/// Use this operator when you want to wait for a pause in the delivery of events from
|
||||
/// the upstream publisher. For example, call `debounce` on the publisher from a text
|
||||
/// field to only receive elements when the user pauses or stops typing. When they
|
||||
/// start typing again, the `debounce` holds event delivery until the next pause.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - dueTime: The time the publisher should wait before publishing an element.
|
||||
/// - scheduler: The scheduler on which this publisher delivers elements
|
||||
/// - options: Scheduler options that customize this publisher’s delivery
|
||||
/// of elements.
|
||||
/// - Returns: A publisher that publishes events only after a specified time elapses.
|
||||
public func debounce<Context: Scheduler>(
|
||||
for dueTime: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil
|
||||
) -> Publishers.Debounce<Self, Context> {
|
||||
return .init(upstream: self,
|
||||
dueTime: dueTime,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes elements only after a specified time interval elapses
|
||||
/// between events.
|
||||
public struct Debounce<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The amount of time the publisher should wait before publishing an element.
|
||||
public let dueTime: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler on which this publisher delivers elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// Scheduler options that customize this publisher’s delivery of elements.
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
dueTime: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.upstream = upstream
|
||||
self.dueTime = dueTime
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
dueTime: dueTime,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Debounce {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private typealias Generation = UInt64
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let dueTime: Context.SchedulerTimeType.Stride
|
||||
|
||||
private let scheduler: Context
|
||||
|
||||
private let options: Context.SchedulerOptions?
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var currentCanceller: Cancellable?
|
||||
|
||||
private var currentValue: Output?
|
||||
|
||||
private var currentGeneration: Generation = 0
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
init(downstream: Downstream,
|
||||
dueTime: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.downstream = downstream
|
||||
self.dueTime = dueTime
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
precondition(!state.isAwaitingSubscription)
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
currentGeneration += 1
|
||||
let generation = currentGeneration
|
||||
currentValue = input
|
||||
let due = scheduler.now.advanced(by: dueTime)
|
||||
lock.unlock()
|
||||
let newCanceller = scheduler.schedule(after: due,
|
||||
interval: dueTime,
|
||||
tolerance: scheduler.minimumTolerance,
|
||||
options: options) { [weak self] in
|
||||
self?.due(generation: generation)
|
||||
}
|
||||
lock.lock()
|
||||
let canceller = currentCanceller
|
||||
currentCanceller = newCanceller
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
precondition(!state.isAwaitingSubscription)
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let canceller = currentCanceller
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
scheduler.schedule {
|
||||
self.downstreamLock.lock()
|
||||
self.downstream.receive(completion: completion)
|
||||
self.downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
precondition(!state.isAwaitingSubscription)
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamDemand += demand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case .subscribed(let subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Debounce" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("downstreamDemand", downstreamDemand),
|
||||
("currentValue", currentValue as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
private func due(generation: Generation) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// If this condition holds, it means that no values were received
|
||||
// in this time frame => we should propagate the current value downstream.
|
||||
guard generation == currentGeneration, let value = currentValue else {
|
||||
let canceller = currentCanceller
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
let hasAnyDemand = downstreamDemand > 0
|
||||
if hasAnyDemand {
|
||||
downstreamDemand -= 1
|
||||
}
|
||||
|
||||
let canceller = currentCanceller!
|
||||
lock.unlock()
|
||||
canceller.cancel()
|
||||
|
||||
guard hasAnyDemand else { return }
|
||||
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(value)
|
||||
downstreamLock.unlock()
|
||||
|
||||
if newDemand == .none { return }
|
||||
|
||||
lock.lock()
|
||||
downstreamDemand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,9 +65,9 @@ extension Publishers {
|
||||
Other.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber)
|
||||
subscriber.receive(subscription: inner)
|
||||
other.subscribe(Inner.OtherSubscriber(inner: inner))
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ extension Publisher {
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and
|
||||
/// returns a Boolean value that indicates whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the first element of a stream
|
||||
/// that satisfies the predicate.
|
||||
/// that satifies the predicate.
|
||||
public func first(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.FirstWhere<Self> {
|
||||
@@ -40,7 +40,7 @@ extension Publisher {
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and
|
||||
/// returns a Boolean value that indicates whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the first element of a stream
|
||||
/// that satisfies the predicate.
|
||||
/// that satifies the predicate.
|
||||
public func tryFirst(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFirstWhere<Self> {
|
||||
|
||||
@@ -78,8 +78,9 @@ extension Publishers.FlatMap {
|
||||
/// acquired.
|
||||
private var outerSubscription: Subscription?
|
||||
|
||||
// Must be recursive lock. Probably a bug in Combine.
|
||||
/// The lock for requesting from `outerSubscription`.
|
||||
private let outerLock = UnfairRecursiveLock.allocate()
|
||||
private let outerLock = UnfairLock.allocate()
|
||||
|
||||
/// The lock for modifying the state. All mutable state here should be
|
||||
/// read and modified with this lock acquired.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ignores all upstream elements, but passes along a completion
|
||||
/// Ingores all upstream elements, but passes along a completion
|
||||
/// state (finished or failed).
|
||||
///
|
||||
/// The output type of this publisher is `Never`.
|
||||
|
||||
@@ -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 {}
|
||||
@@ -129,8 +129,8 @@ extension Publishers.ReceiveOn {
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) {
|
||||
self.scheduledReceive(input, downstream: downstream)
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
|
||||
self?.scheduledReceive(input, downstream: downstream)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
@@ -159,8 +159,8 @@ extension Publishers.ReceiveOn {
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) {
|
||||
self.scheduledReceive(completion: completion, downstream: downstream)
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
|
||||
self?.scheduledReceive(completion: completion, downstream: downstream)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ extension Publishers {
|
||||
/// for purposes of filtering.
|
||||
public let predicate: (Output, Output) -> Bool
|
||||
|
||||
/// Creates a publisher that publishes only elements that don’t match the previous
|
||||
/// Creates a publisher that publishes only elements that don’t match the previou
|
||||
/// element, as evaluated by a provided closure.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces nil elements in the stream with the provided element.
|
||||
/// Replaces nil elements in the stream with the proviced element.
|
||||
///
|
||||
/// - Parameter output: The element to use when replacing `nil`.
|
||||
/// - Returns: A publisher that replaces `nil` elements from
|
||||
|
||||
@@ -9,7 +9,7 @@ extension Publisher {
|
||||
|
||||
/// Returns a publisher as a class instance.
|
||||
///
|
||||
/// The downstream subscriber receives elements and completion states unchanged from
|
||||
/// The downstream subscriber receieves elements and completion states unchanged from
|
||||
/// the upstream publisher. Use this operator when you want to use
|
||||
/// reference semantics, such as storing a publisher instance in a property.
|
||||
///
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
//
|
||||
// Publishers.SwitchToLatest.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 07.01.2020.
|
||||
//
|
||||
|
||||
extension Publisher where Output: Publisher, Output.Failure == Failure {
|
||||
|
||||
/// Flattens the stream of events from multiple upstream publishers to appear as if
|
||||
/// they were coming from a single stream of events.
|
||||
///
|
||||
/// This operator switches the inner publisher as new ones arrive but keeps the outer
|
||||
/// one constant for downstream subscribers.
|
||||
/// For example, given the type `Publisher<Publisher<Data, NSError>, Never>`,
|
||||
/// calling `switchToLatest()` will result in the type `Publisher<Data, NSError>`.
|
||||
/// The downstream subscriber sees a continuous stream of values even though they may
|
||||
/// be coming from different upstream publishers.
|
||||
public func switchToLatest() -> Publishers.SwitchToLatest<Output, Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that “flattens” nested publishers.
|
||||
///
|
||||
/// Given a publisher that publishes Publishers, the `SwitchToLatest` publisher
|
||||
/// produces a sequence of events from only the most recent one.
|
||||
///
|
||||
/// For example, given the type `Publisher<Publisher<Data, NSError>, Never>`,
|
||||
/// calling `switchToLatest()` will result in the type `Publisher<Data, NSError>`.
|
||||
/// The downstream subscriber sees a continuous stream of values even though they may
|
||||
/// be coming from different upstream publishers.
|
||||
public struct SwitchToLatest<NestedPublisher: Publisher, Upstream: Publisher>
|
||||
: Publisher
|
||||
where Upstream.Output == NestedPublisher,
|
||||
Upstream.Failure == NestedPublisher.Failure
|
||||
{
|
||||
public typealias Output = NestedPublisher.Output
|
||||
|
||||
public typealias Failure = NestedPublisher.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Creates a publisher that “flattens” nested publishers.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let outer = Outer(downstream: subscriber)
|
||||
subscriber.receive(subscription: outer)
|
||||
upstream.subscribe(outer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SwitchToLatest {
|
||||
fileprivate final class Outer<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == NestedPublisher.Output,
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
private var outerSubscription: Subscription?
|
||||
private var currentInnerSubscription: Subscription?
|
||||
private var currentInnerIndex: UInt64 = 0
|
||||
private var nextInnerIndex: UInt64 = 1
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
private var cancelled = false
|
||||
private var finished = false
|
||||
private var sentCompletion = false
|
||||
private var awaitingInnerSubscription = false
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard outerSubscription == nil && !cancelled else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
outerSubscription = subscription
|
||||
lock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if cancelled || finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
if let currentInnerSubscription = self.currentInnerSubscription {
|
||||
self.currentInnerSubscription = nil
|
||||
lock.unlock()
|
||||
currentInnerSubscription.cancel()
|
||||
lock.lock()
|
||||
}
|
||||
|
||||
let index = nextInnerIndex
|
||||
currentInnerIndex = index
|
||||
nextInnerIndex += 1
|
||||
awaitingInnerSubscription = true
|
||||
lock.unlock()
|
||||
input.subscribe(Side(inner: self, index: index))
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
outerSubscription = nil
|
||||
finished = true
|
||||
|
||||
if cancelled {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
switch completion {
|
||||
case .finished:
|
||||
if awaitingInnerSubscription {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if currentInnerSubscription == nil {
|
||||
sentCompletion = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure:
|
||||
let currentInnerSubscription = self.currentInnerSubscription
|
||||
self.currentInnerSubscription = nil
|
||||
sentCompletion = true
|
||||
lock.unlock()
|
||||
currentInnerSubscription?.cancel()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
downstreamDemand += demand
|
||||
if let currentInnerSubscription = self.currentInnerSubscription {
|
||||
lock.unlock()
|
||||
currentInnerSubscription.request(demand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
cancelled = true
|
||||
let currentInnerSubscription = self.currentInnerSubscription
|
||||
self.currentInnerSubscription = nil
|
||||
let outerSubscription = self.outerSubscription
|
||||
self.outerSubscription = nil
|
||||
lock.unlock()
|
||||
|
||||
currentInnerSubscription?.cancel()
|
||||
outerSubscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "SwitchToLatest" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
private func receiveInner(subscription: Subscription, _ index: UInt64) {
|
||||
lock.lock()
|
||||
guard currentInnerIndex == index &&
|
||||
!cancelled &&
|
||||
currentInnerSubscription == nil else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
currentInnerSubscription = subscription
|
||||
awaitingInnerSubscription = false
|
||||
let downstreamDemand = self.downstreamDemand
|
||||
lock.unlock()
|
||||
if downstreamDemand > 0 {
|
||||
subscription.request(downstreamDemand)
|
||||
}
|
||||
}
|
||||
|
||||
private func receiveInner(_ input: NestedPublisher.Output,
|
||||
_ index: UInt64) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard currentInnerIndex == index && !cancelled else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
// This will crash if we don't have any demand yet.
|
||||
// Combine crashes here too.
|
||||
downstreamDemand -= 1
|
||||
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
if newDemand > 0 {
|
||||
lock.lock()
|
||||
downstreamDemand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
return newDemand
|
||||
}
|
||||
|
||||
private func receiveInner(completion: Subscribers.Completion<Failure>,
|
||||
_ index: UInt64) {
|
||||
lock.lock()
|
||||
guard currentInnerIndex == index && !cancelled else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
precondition(!awaitingInnerSubscription, "Unexpected completion")
|
||||
currentInnerSubscription = nil
|
||||
switch completion {
|
||||
case .finished:
|
||||
if sentCompletion || !finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
sentCompletion = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
case .failure:
|
||||
if sentCompletion {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
let outerSubscription = self.outerSubscription
|
||||
self.outerSubscription = nil
|
||||
sentCompletion = true
|
||||
lock.unlock()
|
||||
outerSubscription?.cancel()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SwitchToLatest.Outer {
|
||||
private struct Side
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NestedPublisher.Output
|
||||
|
||||
typealias Failure = NestedPublisher.Failure
|
||||
|
||||
typealias Outer =
|
||||
Publishers.SwitchToLatest<NestedPublisher, Upstream>.Outer<Downstream>
|
||||
|
||||
private let index: UInt64
|
||||
private let outer: Outer
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(inner: Outer, index: UInt64) {
|
||||
self.index = index
|
||||
self.outer = inner
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
outer.receiveInner(subscription: subscription, index)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return outer.receiveInner(input, index)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
outer.receiveInner(completion: completion, index)
|
||||
}
|
||||
|
||||
var description: String { return "SwitchToLatest" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(
|
||||
("parentSubscription", outer.combineIdentifier)
|
||||
)
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
//
|
||||
// Publishers.Timeout.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 14.06.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Terminates publishing if the upstream publisher exceeds the specified time
|
||||
/// interval without producing an element.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The maximum time interval the publisher can go without emitting
|
||||
/// an element, expressed in the time system of the scheduler.
|
||||
/// - scheduler: The scheduler to deliver events on.
|
||||
/// - options: Scheduler options that customize the delivery of elements.
|
||||
/// - customError: A closure that executes if the publisher times out.
|
||||
/// The publisher sends the failure returned by this closure to the subscriber as
|
||||
/// the reason for termination.
|
||||
/// - Returns: A publisher that terminates if the specified interval elapses with no
|
||||
/// events received from the upstream publisher.
|
||||
public func timeout<Context: Scheduler>(
|
||||
_ interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil,
|
||||
customError: (() -> Self.Failure)? = nil
|
||||
) -> Publishers.Timeout<Self, Context> {
|
||||
return .init(upstream: self,
|
||||
interval: interval,
|
||||
scheduler: scheduler,
|
||||
options: options,
|
||||
customError: customError)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Timeout<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
public let scheduler: Context
|
||||
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public let customError: (() -> Upstream.Failure)?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?,
|
||||
customError: (() -> Publishers.Timeout<Upstream, Context>.Failure)?) {
|
||||
self.upstream = upstream
|
||||
self.interval = interval
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
self.customError = customError
|
||||
}
|
||||
|
||||
public func receive<Downsteam: Subscriber>(subscriber: Downsteam)
|
||||
where Downsteam.Failure == Failure, Downsteam.Input == Output
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
interval: interval,
|
||||
scheduler: scheduler,
|
||||
options: options,
|
||||
customError: customError)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Timeout {
|
||||
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 lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
private let scheduler: Context
|
||||
|
||||
private let options: Context.SchedulerOptions?
|
||||
|
||||
private let customError: (() -> Upstream.Failure)?
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var didTimeout = false
|
||||
|
||||
private var timer: AnyCancellable?
|
||||
|
||||
init(downstream: Downstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?,
|
||||
customError: (() -> Upstream.Failure)?) {
|
||||
self.downstream = downstream
|
||||
self.interval = interval
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
self.customError = customError
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
timer = timeoutClock()
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard !didTimeout, case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
timer?.cancel()
|
||||
didTimeout = false
|
||||
timer = timeoutClock()
|
||||
lock.unlock()
|
||||
scheduler.schedule(options: options) {
|
||||
self.downstreamLock.lock()
|
||||
_ = self.downstream.receive(input)
|
||||
self.downstreamLock.unlock()
|
||||
}
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
timer?.cancel()
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
scheduler.schedule(options: options) {
|
||||
self.downstreamLock.lock()
|
||||
self.downstream.receive(completion: completion)
|
||||
self.downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Timeout" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
private func timedOut() {
|
||||
lock.lock()
|
||||
guard !didTimeout, case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
didTimeout = true
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstreamLock.lock()
|
||||
downstream
|
||||
.receive(completion: customError.map { .failure($0()) } ?? .finished)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
private func timeoutClock() -> AnyCancellable {
|
||||
let cancellable = scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: interval),
|
||||
interval: interval,
|
||||
tolerance: scheduler.minimumTolerance,
|
||||
options: options,
|
||||
{ [weak self] in self?.timedOut() })
|
||||
return AnyCancellable { cancellable.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public protocol SchedulerTimeIntervalConvertible {
|
||||
///
|
||||
/// A scheduler used to execute code as soon as possible, or after a future date.
|
||||
/// Individual scheduler implementations use whatever time-keeping system makes sense
|
||||
/// for them. Schedulers express this as their `SchedulerTimeType`. Since this type
|
||||
/// for them. Schdedulers express this as their `SchedulerTimeType`. Since this type
|
||||
/// conforms to `SchedulerTimeIntervalConvertible`, you can always express these times
|
||||
/// with the convenience functions like `.milliseconds(500)`. Schedulers can accept
|
||||
/// options to control how they execute the actions passed to them. These options may
|
||||
|
||||
@@ -27,7 +27,7 @@ public protocol Subscriber: CustomCombineIdentifierConvertible {
|
||||
/// Tells the subscriber that the publisher has produced an element.
|
||||
///
|
||||
/// - Parameter input: The published element.
|
||||
/// - Returns: A `Demand` instance indicating how many more elements the subscriber
|
||||
/// - Returns: A `Demand` instance indicating how many more elements the subcriber
|
||||
/// expects to receive.
|
||||
func receive(_ input: Input) -> Subscribers.Demand
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the result of adding two demands.
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
@@ -78,7 +77,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds two demands, and assigns the result to the first demand.
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
@@ -87,7 +85,6 @@ extension Subscribers {
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
|
||||
/// Returns the result of adding an integer to a demand.
|
||||
/// When adding any value to` .unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
@@ -99,7 +96,6 @@ extension Subscribers {
|
||||
return isOverflow ? .unlimited : .max(sum)
|
||||
}
|
||||
|
||||
/// Adds an integer to a demand, and assigns the result to the demand.
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
@@ -107,9 +103,6 @@ extension Subscribers {
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
|
||||
/// Returns the result of multiplying a demand by an integer.
|
||||
/// When multiplying any value by `.unlimited`, the result is `.unlimited`. If
|
||||
/// the multiplication operation overflows, the result is `.unlimited`.
|
||||
public static func * (lhs: Demand, rhs: Int) -> Demand {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
@@ -119,16 +112,12 @@ extension Subscribers {
|
||||
return isOverflow ? .unlimited : .max(product)
|
||||
}
|
||||
|
||||
/// Multiplies a demand by an integer, and assigns the result to the demand.
|
||||
/// When multiplying any value by `.unlimited`, the result is `.unlimited`. If
|
||||
/// the multiplication operation overflows, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func *= (lhs: inout Demand, rhs: Int) {
|
||||
lhs = lhs * rhs
|
||||
}
|
||||
|
||||
/// Returns the result of subtracting one demand from another.
|
||||
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
|
||||
/// the result is still `.unlimited`. Subtracting `.unlimited` from any value
|
||||
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
|
||||
@@ -148,7 +137,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtracts one demand from another, and assigns the result to the first demand.
|
||||
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
|
||||
/// the result is still `.unlimited`. Subtracting unlimited from any value
|
||||
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
|
||||
@@ -160,7 +148,6 @@ extension Subscribers {
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
/// Returns the result of subtracting an integer from a demand.
|
||||
/// When subtracting any value from `.unlimited`, the result is still
|
||||
/// `.unlimited`.
|
||||
/// A negative demand is not possible; any operation that would result in
|
||||
@@ -177,7 +164,6 @@ extension Subscribers {
|
||||
return isOverflow ? .none : .max(difference)
|
||||
}
|
||||
|
||||
/// Subtracts an integer from a demand, and assigns the result to the demand.
|
||||
/// When subtracting any value from `.unlimited,` the result is still
|
||||
/// `.unlimited`.
|
||||
/// A negative demand is not possible; any operation that would result in
|
||||
@@ -189,10 +175,6 @@ extension Subscribers {
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates whether the demand requests more than
|
||||
/// the given number of elements.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `true`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `rhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Demand, rhs: Int) -> Bool {
|
||||
@@ -203,10 +185,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates whether the first demand requests more or
|
||||
/// the same number of elements as the second.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `true`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `rhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Demand, rhs: Int) -> Bool {
|
||||
@@ -217,10 +195,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates a given number of elements is greater than
|
||||
/// the maximum specified by the demand.
|
||||
/// If `rhs` is `.unlimited`, then the result is always `false`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `lhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Int, rhs: Demand) -> Bool {
|
||||
@@ -231,10 +205,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates a given number of elements is greater than
|
||||
/// or equal to the maximum specified by the demand.
|
||||
/// If `rhs` is `.unlimited`, then the result is always `false`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `lhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Int, rhs: Demand) -> Bool {
|
||||
@@ -245,10 +215,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates whether the demand requests fewer than
|
||||
/// the given number of elements.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `false`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `rhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Demand, rhs: Int) -> Bool {
|
||||
@@ -259,10 +225,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates a given number of elements is less than
|
||||
/// the maximum specified by the demand.
|
||||
/// If `rhs` is `.unlimited`, then the result is always `true`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `lhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Int, rhs: Demand) -> Bool {
|
||||
@@ -273,10 +235,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates whether the demand requests fewer or
|
||||
/// the same number of elements as the given integer.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `false`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `rhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Demand, rhs: Int) -> Bool {
|
||||
@@ -287,10 +245,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value that indicates a given number of elements is less
|
||||
/// than or equal the maximum specified by the demand.
|
||||
/// If `rhs` is `.unlimited`, then the result is always `true`.
|
||||
/// Otherwise, the operator compares the demand’s `max` value to `lhs`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Int, rhs: Demand) -> Bool {
|
||||
@@ -301,12 +255,9 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value that indicates whether the first demand requests fewer
|
||||
/// elements than the second.
|
||||
/// If both sides are `.unlimited`, the result is always `false`.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `false`.
|
||||
/// If `rhs` is `.unlimited`, then the result is always `true`.
|
||||
/// Otherwise, this operator compares the demands’ `max` values.
|
||||
/// If `rhs` is `.unlimited` then the result is `false` iff `lhs` is `.unlimited`
|
||||
/// Otherwise, the two `.max` values are compared.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Demand, rhs: Demand) -> Bool {
|
||||
@@ -320,12 +271,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value that indicates whether the first demand requests fewer
|
||||
/// or the same number of elements as the second.
|
||||
/// If both sides are `.unlimited`, the result is always `true`.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `false`.
|
||||
/// If `rhs` is `.unlimited` then the result is always `true`.
|
||||
/// Otherwise, this operator compares the demands’ `max` values.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Demand, rhs: Demand) -> Bool {
|
||||
@@ -341,12 +286,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates whether the first demand requests more or
|
||||
/// the same number of elements as the second.
|
||||
/// If both sides are `.unlimited`, the result is always `false`.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `true`.
|
||||
/// If rhs is `.unlimited` then the result is always `false`.
|
||||
/// Otherwise, this operator compares the demands’ `max` values.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Demand, rhs: Demand) -> Bool {
|
||||
@@ -362,12 +301,6 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean that indicates whether the first demand requests more
|
||||
/// elements than the second.
|
||||
/// If both sides are `.unlimited`, the result is always `false`.
|
||||
/// If `lhs` is `.unlimited`, then the result is always `true`.
|
||||
/// If `rhs` is `.unlimited` then the result is always `false`.
|
||||
/// Otherwise, this operator compares the demands’ `max` values.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Demand, rhs: Demand) -> Bool {
|
||||
@@ -383,9 +316,8 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value indicating whether a demand requests the given number
|
||||
/// of elements.
|
||||
/// An `.unlimited` demand doesn’t match any integer.
|
||||
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
|
||||
/// integer.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func == (lhs: Demand, rhs: Int) -> Bool {
|
||||
@@ -396,9 +328,8 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value indicating whether a demand is not equal to
|
||||
/// an integer.
|
||||
/// The `.unlimited` value isn’t equal to any integer.
|
||||
/// 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 {
|
||||
@@ -408,9 +339,8 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value indicating whether a given number of elements matches
|
||||
/// the request of a given demand.
|
||||
/// An `.unlimited` demand doesn’t match any integer.
|
||||
/// 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 {
|
||||
@@ -420,9 +350,8 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Boolean value indicating whether an integer is not equal to
|
||||
/// a demand.
|
||||
/// The `.unlimited` value isn’t equal to any integer.
|
||||
/// 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 {
|
||||
@@ -437,7 +366,7 @@ extension Subscribers {
|
||||
return lhs.rawValue == rhs.rawValue
|
||||
}
|
||||
|
||||
/// The number of requested values, or nil if `.unlimited`.
|
||||
/// Returns the number of requested values, or `nil` if `.unlimited`.
|
||||
@inlinable public var max: Int? {
|
||||
if self == .unlimited {
|
||||
return nil
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
/// A protocol representing the connection of a subscriber to a publisher.
|
||||
///
|
||||
/// Subscriptions are class constrained because a `Subscription` has identity -
|
||||
/// Subcriptions are class constrained because a `Subscription` has identity -
|
||||
/// defined by the moment in time a particular subscriber attached to a publisher.
|
||||
/// Canceling a `Subscription` must be thread-safe.
|
||||
///
|
||||
|
||||
@@ -166,7 +166,7 @@ extension DispatchQueue {
|
||||
// as possible.
|
||||
//
|
||||
// By trial and error I got that the `rawValue` of `UInt64.max / 13`
|
||||
// gives us probably the widest range of supported values:
|
||||
// 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.
|
||||
@@ -344,10 +344,8 @@ extension DispatchQueue {
|
||||
#if !canImport(Combine)
|
||||
extension DispatchQueue: OpenCombine.Scheduler {
|
||||
|
||||
/// Options that affect the operation of the dispatch queue scheduler.
|
||||
public typealias SchedulerOptions = OCombine.SchedulerOptions
|
||||
|
||||
/// The scheduler time type used by the dispatch queue.
|
||||
public typealias SchedulerTimeType = OCombine.SchedulerTimeType
|
||||
|
||||
public var minimumTolerance: OCombine.SchedulerTimeType.Stride {
|
||||
@@ -384,7 +382,7 @@ extension DispatchQueue: OpenCombine.Scheduler {
|
||||
}
|
||||
#endif
|
||||
|
||||
// This function is taken from swift-corelibs-libdispatch:
|
||||
// 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].
|
||||
|
||||
@@ -44,7 +44,7 @@ extension NotificationCenter {
|
||||
/// The name of notifications published by this publisher.
|
||||
public let name: Notification.Name
|
||||
|
||||
/// The object posting the named notification.
|
||||
/// The object posting the named notfication.
|
||||
public let object: AnyObject?
|
||||
|
||||
/// Creates a publisher that emits events when broadcasting notifications.
|
||||
@@ -52,7 +52,7 @@ extension NotificationCenter {
|
||||
/// - Parameters:
|
||||
/// - center: The notification center to publish notifications for.
|
||||
/// - name: The name of the notification to publish.
|
||||
/// - object: The object posting the named notification. If `nil`,
|
||||
/// - 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,
|
||||
@@ -78,7 +78,7 @@ extension NotificationCenter {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: The name of the notification to publish.
|
||||
/// - object: The object posting the named notification. If `nil`, the publisher
|
||||
/// - 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.
|
||||
@@ -116,7 +116,7 @@ extension NotificationCenter {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: The name of the notification to publish.
|
||||
/// - object: The object posting the named notification. If `nil`, the publisher
|
||||
/// - 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,
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
//
|
||||
// OperationQueue+Scheduler.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 14.06.2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension OperationQueue {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Foundation overlay for Combine extends `OperationQueue` with new methods and
|
||||
/// nested types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `OperationQueue.SchedulerTimeType`,
|
||||
/// because Swift is unable to understand which `SchedulerTimeType`
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `OperationQueue.OCombine.SchedulerTimeType`.
|
||||
///
|
||||
/// 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: Scheduler {
|
||||
|
||||
public let queue: OperationQueue
|
||||
|
||||
public init(_ queue: OperationQueue) {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
/// The scheduler time type used by the operation queue.
|
||||
public struct SchedulerTimeType: Strideable, Codable, Hashable {
|
||||
|
||||
/// The date represented by this type.
|
||||
public var date: Date
|
||||
|
||||
/// Initializes a operation queue scheduler time with the given date.
|
||||
///
|
||||
/// - Parameter date: The date to represent.
|
||||
public init(_ date: Date) {
|
||||
self.date = date
|
||||
}
|
||||
|
||||
/// Returns the distance to another operation queue scheduler time.
|
||||
///
|
||||
/// - Parameter other: Another operation queue time.
|
||||
/// - Returns: The time interval between this time and the provided time.
|
||||
public func distance(to other: SchedulerTimeType) -> Stride {
|
||||
let absoluteSelf = date.timeIntervalSinceReferenceDate
|
||||
let absoluteOther = other.date.timeIntervalSinceReferenceDate
|
||||
return Stride(absoluteSelf.distance(to: absoluteOther))
|
||||
}
|
||||
|
||||
/// Returns an operation queue scheduler time calculated by advancing this
|
||||
/// instance’s time by the given interval.
|
||||
///
|
||||
/// - Parameter n: A time interval to advance.
|
||||
/// - Returns: An operation queue time advanced by the given interval from
|
||||
/// this instance’s time.
|
||||
public func advanced(by value: Stride) -> SchedulerTimeType {
|
||||
return SchedulerTimeType(date + value.magnitude)
|
||||
}
|
||||
|
||||
/// The interval by which operation queue times advance.
|
||||
public struct Stride: SchedulerTimeIntervalConvertible,
|
||||
Comparable,
|
||||
SignedNumeric,
|
||||
ExpressibleByFloatLiteral,
|
||||
Codable {
|
||||
|
||||
public typealias FloatLiteralType = TimeInterval
|
||||
|
||||
public typealias IntegerLiteralType = TimeInterval
|
||||
|
||||
public typealias Magnitude = TimeInterval
|
||||
|
||||
/// The value of this time interval in seconds.
|
||||
public var magnitude: TimeInterval
|
||||
|
||||
/// The value of this time interval in seconds.
|
||||
public var timeInterval: TimeInterval {
|
||||
return magnitude
|
||||
}
|
||||
|
||||
public init(integerLiteral value: TimeInterval) {
|
||||
magnitude = value
|
||||
}
|
||||
|
||||
public init(floatLiteral value: TimeInterval) {
|
||||
magnitude = value
|
||||
}
|
||||
|
||||
public init(_ timeInterval: TimeInterval) {
|
||||
magnitude = timeInterval
|
||||
}
|
||||
|
||||
public init?<Source: BinaryInteger>(exactly source: Source) {
|
||||
guard let value = TimeInterval(exactly: source) else { return nil }
|
||||
magnitude = value
|
||||
}
|
||||
|
||||
public static func < (lhs: Stride, rhs: Stride) -> Bool {
|
||||
return lhs.magnitude < rhs.magnitude
|
||||
}
|
||||
|
||||
public static func * (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(lhs.magnitude * rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func + (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(lhs.magnitude + rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func - (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(lhs.magnitude - rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func *= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude *= rhs.magnitude
|
||||
}
|
||||
|
||||
public static func += (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude += rhs.magnitude
|
||||
}
|
||||
|
||||
public static func -= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude -= rhs.magnitude
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value))
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Double) -> Stride {
|
||||
return Stride(TimeInterval(value))
|
||||
}
|
||||
|
||||
public static func milliseconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value) / 1_000)
|
||||
}
|
||||
|
||||
public static func microseconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value) / 1_000_000)
|
||||
}
|
||||
|
||||
public static func nanoseconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value) / 1_000_000_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Options that affect the operation of the operation queue scheduler.
|
||||
public struct SchedulerOptions {
|
||||
}
|
||||
|
||||
private final class DelayReadyOperation: Operation, Cancellable {
|
||||
|
||||
private static let readySchedulingQueue =
|
||||
DispatchQueue(label: "DelayReadyOperation")
|
||||
|
||||
private var action: (() -> Void)?
|
||||
|
||||
private var readyFromAfter = false
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
init(_ action: @escaping() -> Void, after: SchedulerTimeType) {
|
||||
self.action = action
|
||||
super.init()
|
||||
let deadline = DispatchTime.now() + after.date.timeIntervalSinceNow
|
||||
DelayReadyOperation.readySchedulingQueue
|
||||
.asyncAfter(deadline: deadline) { [weak self] in
|
||||
self?.becomeReady()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
action!()
|
||||
action = nil
|
||||
}
|
||||
|
||||
private func becomeReady() {
|
||||
// Smart key paths don't work with NSOperation in swift-corelibs-foundation prior to
|
||||
// Swift 5.1.
|
||||
#if canImport(Darwin) || swift(<5.1)
|
||||
// The smart key paths don't work with NSOperation on OS versions prior to
|
||||
// iOS 11. The string key paths work fine everywhere.
|
||||
// https://forums.swift.org/t/keypath-translation-for-kvo-notification-seems-to-not-work-properly-on-ios-10/15898
|
||||
willChangeValue(forKey: "isReady")
|
||||
#else
|
||||
willChangeValue(for: \.isReady)
|
||||
#endif
|
||||
lock.lock()
|
||||
readyFromAfter = true
|
||||
lock.unlock()
|
||||
// Smart key paths don't work with NSOperation in swift-corelibs-foundation prior to
|
||||
// Swift 5.1.
|
||||
#if canImport(Darwin) || swift(<5.1)
|
||||
// The smart key paths don't work with NSOperation on OS versions prior to
|
||||
// iOS 11. The string key paths work fine everywhere.
|
||||
// https://forums.swift.org/t/keypath-translation-for-kvo-notification-seems-to-not-work-properly-on-ios-10/15898
|
||||
didChangeValue(forKey: "isReady")
|
||||
#else
|
||||
didChangeValue(for: \.isReady)
|
||||
#endif
|
||||
}
|
||||
|
||||
override var isReady: Bool {
|
||||
guard super.isReady else { return false }
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return readyFromAfter
|
||||
}
|
||||
}
|
||||
|
||||
public func schedule(options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
let op = BlockOperation(block: action)
|
||||
queue.addOperation(op)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
let op = DelayReadyOperation(action, after: date)
|
||||
queue.addOperation(op)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
let op = DelayReadyOperation(action, after: date.advanced(by: interval))
|
||||
queue.addOperation(op)
|
||||
return AnyCancellable(op)
|
||||
}
|
||||
|
||||
public var now: SchedulerTimeType {
|
||||
return .init(Date())
|
||||
}
|
||||
|
||||
public var minimumTolerance: SchedulerTimeType.Stride {
|
||||
return .init(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Foundation are imported.
|
||||
///
|
||||
/// Foundation overlay for Combine extends `OperationQueue` with new methods and
|
||||
/// nested types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `OperationQueue.main.schedule { doThings() }`,
|
||||
/// because Swift is unable to understand which `schedule` method
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `OperationQueue.main.ocombine.schedule { doThings() }`.
|
||||
///
|
||||
/// 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 OCombine(self)
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
extension OperationQueue: OpenCombine.Scheduler {
|
||||
|
||||
/// Options that affect the operation of the run loop scheduler.
|
||||
public typealias SchedulerOptions = OCombine.SchedulerOptions
|
||||
|
||||
/// The scheduler time type used by the run loop.
|
||||
public typealias SchedulerTimeType = OCombine.SchedulerTimeType
|
||||
|
||||
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
|
||||
ocombine.schedule(options: options, action)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
ocombine.schedule(after: date, tolerance: tolerance, options: options, action)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
return ocombine.schedule(after: date,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
options: options,
|
||||
action)
|
||||
}
|
||||
|
||||
public var now: SchedulerTimeType {
|
||||
return ocombine.now
|
||||
}
|
||||
|
||||
public var minimumTolerance: SchedulerTimeType.Stride {
|
||||
return ocombine.minimumTolerance
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -8,9 +8,6 @@
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
// PropertyListEncoder and PropertyListDecoder are unavailable in
|
||||
// swift-corelibs-foundation prior to Swift 5.1.
|
||||
#if canImport(Darwin) || swift(>=5.1)
|
||||
extension PropertyListEncoder: TopLevelEncoder {
|
||||
public typealias Output = Data
|
||||
}
|
||||
@@ -18,4 +15,3 @@ extension PropertyListEncoder: TopLevelEncoder {
|
||||
extension PropertyListDecoder: TopLevelDecoder {
|
||||
public typealias Input = Data
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
//
|
||||
// RunLoop+Scheduler.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 13.12.2019.
|
||||
//
|
||||
|
||||
import CoreFoundation
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension RunLoop {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Foundation overlay for Combine extends `RunLoop` with new methods and nested
|
||||
/// types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `RunLoop.SchedulerTimeType`,
|
||||
/// because Swift is unable to understand which `SchedulerTimeType`
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `RunLoop.OCombine.SchedulerTimeType`.
|
||||
///
|
||||
/// 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: Scheduler {
|
||||
|
||||
public let runLoop: RunLoop
|
||||
|
||||
public init(_ runLoop: RunLoop) {
|
||||
self.runLoop = runLoop
|
||||
}
|
||||
|
||||
/// The scheduler time type used by the run loop.
|
||||
public struct SchedulerTimeType: Strideable, Codable, Hashable {
|
||||
|
||||
/// The date represented by this type.
|
||||
public var date: Date
|
||||
|
||||
/// Initializes a run loop scheduler time with the given date.
|
||||
///
|
||||
/// - Parameter date: The date to represent.
|
||||
public init(_ date: Date) {
|
||||
self.date = date
|
||||
}
|
||||
|
||||
/// Returns the distance to another run loop scheduler time.
|
||||
///
|
||||
/// - Parameter other: Another run loop time.
|
||||
/// - Returns: The time interval between this time and the provided time.
|
||||
public func distance(to other: SchedulerTimeType) -> Stride {
|
||||
let absoluteSelf = date.timeIntervalSinceReferenceDate
|
||||
let absoluteOther = other.date.timeIntervalSinceReferenceDate
|
||||
return Stride(absoluteSelf.distance(to: absoluteOther))
|
||||
}
|
||||
|
||||
/// Returns a run loop scheduler time calculated by advancing this instance’s
|
||||
/// time by the given interval.
|
||||
///
|
||||
/// - Parameter value: A time interval to advance.
|
||||
/// - Returns: A run loop time advanced by the given interval from this
|
||||
/// instance’s time.
|
||||
public func advanced(by value: Stride) -> SchedulerTimeType {
|
||||
return SchedulerTimeType(date + value.magnitude)
|
||||
}
|
||||
|
||||
/// The interval by which run loop times advance.
|
||||
public struct Stride: SchedulerTimeIntervalConvertible,
|
||||
Comparable,
|
||||
SignedNumeric,
|
||||
ExpressibleByFloatLiteral,
|
||||
Codable {
|
||||
|
||||
public typealias FloatLiteralType = TimeInterval
|
||||
|
||||
public typealias IntegerLiteralType = TimeInterval
|
||||
|
||||
/// A type that can represent the absolute value of any possible value
|
||||
/// of the conforming type.
|
||||
public typealias Magnitude = TimeInterval
|
||||
|
||||
/// The value of this time interval in seconds.
|
||||
public var magnitude: TimeInterval
|
||||
|
||||
/// The value of this time interval in seconds.
|
||||
public var timeInterval: TimeInterval { return magnitude }
|
||||
|
||||
public init(integerLiteral value: TimeInterval) {
|
||||
self.magnitude = value
|
||||
}
|
||||
|
||||
public init(floatLiteral value: TimeInterval) {
|
||||
self.magnitude = value
|
||||
}
|
||||
|
||||
public init(_ timeInterval: TimeInterval) {
|
||||
self.magnitude = timeInterval
|
||||
}
|
||||
|
||||
public init?<Source: BinaryInteger>(exactly source: Source) {
|
||||
guard let value = TimeInterval(exactly: source) else { return nil }
|
||||
magnitude = value
|
||||
}
|
||||
|
||||
public static func < (lhs: Stride, rhs: Stride) -> Bool {
|
||||
return lhs.magnitude < rhs.magnitude
|
||||
}
|
||||
|
||||
public static func * (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(lhs.magnitude * rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func + (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(lhs.magnitude + rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func - (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(lhs.magnitude - rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func *= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude *= rhs.magnitude
|
||||
}
|
||||
|
||||
public static func += (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude += rhs.magnitude
|
||||
}
|
||||
|
||||
public static func -= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude -= rhs.magnitude
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value))
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Double) -> Stride {
|
||||
return Stride(TimeInterval(value))
|
||||
}
|
||||
|
||||
public static func milliseconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value) / 1_000)
|
||||
}
|
||||
|
||||
public static func microseconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value) / 1_000_000)
|
||||
}
|
||||
|
||||
public static func nanoseconds(_ value: Int) -> Stride {
|
||||
return Stride(TimeInterval(value) / 1_000_000_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Options that affect the operation of the run loop scheduler.
|
||||
public struct SchedulerOptions {
|
||||
}
|
||||
|
||||
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
|
||||
let cfRunLoop = runLoop.getCFRunLoop()
|
||||
CFRunLoopPerformBlock(cfRunLoop, defaultRunLoopModeString, action)
|
||||
CFRunLoopWakeUp(cfRunLoop)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
let timer = CFRunLoopTimerCreateWithHandler(
|
||||
nil,
|
||||
date.date.timeIntervalSinceReferenceDate,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
{ _ in action() }
|
||||
)
|
||||
// A bug in Combine. The schedule(after:tolerance:options:_:) methods
|
||||
// always executes the action on the current runloop.
|
||||
// (FB7493579 if Apple folks are watching)
|
||||
let theWrongRunLoop = CFRunLoopGetCurrent()
|
||||
CFRunLoopAddTimer(theWrongRunLoop, timer, defaultRunLoopMode)
|
||||
CFRunLoopWakeUp(theWrongRunLoop)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
let timer = CFRunLoopTimerCreateWithHandler(
|
||||
nil,
|
||||
date.date.timeIntervalSinceReferenceDate,
|
||||
interval.magnitude,
|
||||
0,
|
||||
0,
|
||||
{ _ in action() }
|
||||
)
|
||||
let cfRunLoop = runLoop.getCFRunLoop()
|
||||
CFRunLoopAddTimer(cfRunLoop, timer, defaultRunLoopMode)
|
||||
CFRunLoopWakeUp(cfRunLoop)
|
||||
return AnyCancellable { CFRunLoopTimerInvalidate(timer) }
|
||||
}
|
||||
|
||||
public var now: SchedulerTimeType {
|
||||
return .init(Date())
|
||||
}
|
||||
|
||||
public var minimumTolerance: SchedulerTimeType.Stride {
|
||||
return .init(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Foundation are imported.
|
||||
///
|
||||
/// Foundation overlay for Combine extends `RunLoop` with new methods and nested
|
||||
/// types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `RunLoop.main.schedule { doThings() }`,
|
||||
/// because Swift is unable to understand which `schedule` method
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `RunLoop.main.ocombine.schedule { doThings() }`.
|
||||
///
|
||||
/// 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 OCombine(self)
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
extension RunLoop: OpenCombine.Scheduler {
|
||||
|
||||
/// Options that affect the operation of the run loop scheduler.
|
||||
public typealias SchedulerOptions = OCombine.SchedulerOptions
|
||||
|
||||
/// The scheduler time type used by the run loop.
|
||||
public typealias SchedulerTimeType = OCombine.SchedulerTimeType
|
||||
|
||||
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
|
||||
ocombine.schedule(options: options, action)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
ocombine.schedule(after: date, tolerance: tolerance, options: options, action)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
return ocombine.schedule(after: date,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
options: options,
|
||||
action)
|
||||
}
|
||||
|
||||
public var now: SchedulerTimeType {
|
||||
return ocombine.now
|
||||
}
|
||||
|
||||
public var minimumTolerance: SchedulerTimeType.Stride {
|
||||
return ocombine.minimumTolerance
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private var defaultRunLoopMode: CFRunLoopMode {
|
||||
#if canImport(Darwin)
|
||||
return CFRunLoopMode.defaultMode
|
||||
#else
|
||||
return kCFRunLoopDefaultMode
|
||||
#endif
|
||||
}
|
||||
|
||||
private var defaultRunLoopModeString: CFString {
|
||||
#if canImport(Darwin)
|
||||
return CFRunLoopMode.defaultMode.rawValue
|
||||
#else
|
||||
return kCFRunLoopDefaultMode
|
||||
#endif
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
//
|
||||
// Timer+Publisher.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23.06.2020.
|
||||
//
|
||||
|
||||
import CoreFoundation
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension Timer {
|
||||
|
||||
/// Returns a publisher that repeatedly emits the current date on the given interval.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The time interval on which to publish events. For example,
|
||||
/// a value of `0.5` publishes an event approximately every half-second.
|
||||
/// - tolerance: The allowed timing variance when emitting events.
|
||||
/// Defaults to `nil`, which allows any variance.
|
||||
/// - runLoop: The run loop on which the timer runs.
|
||||
/// - mode: The run loop mode in which to run the timer.
|
||||
/// - options: Scheduler options passed to the timer. Defaults to `nil`.
|
||||
/// - Returns: A publisher that repeatedly emits the current date on the given
|
||||
/// interval.
|
||||
public static func publish(
|
||||
every interval: TimeInterval,
|
||||
tolerance _: TimeInterval? = nil,
|
||||
on runLoop: RunLoop,
|
||||
in mode: RunLoop.Mode,
|
||||
options: RunLoop.OCombine.SchedulerOptions? = nil
|
||||
) -> OCombine.TimerPublisher {
|
||||
// A bug in Combine: tolerance is ignored.
|
||||
return .init(interval: interval, runLoop: runLoop, mode: mode, options: options)
|
||||
}
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Foundation overlay for Combine extends `Timer` with new methods and nested
|
||||
/// types.
|
||||
/// If you import both OpenCombine and Foundation, you will not be able
|
||||
/// to write `Timer.TimerPublisher`,
|
||||
/// because Swift is unable to understand which `TimerPublisher`
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `Timer.OCombine.TimerPublisher`.
|
||||
///
|
||||
/// 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 enum OCombine {
|
||||
|
||||
/// A publisher that repeatedly emits the current date on a given interval.
|
||||
public final class TimerPublisher: ConnectablePublisher {
|
||||
public typealias Output = Date
|
||||
public typealias Failure = Never
|
||||
|
||||
public let interval: TimeInterval
|
||||
public let tolerance: TimeInterval?
|
||||
public let runLoop: RunLoop
|
||||
public let mode: RunLoop.Mode
|
||||
public let options: RunLoop.OCombine.SchedulerOptions?
|
||||
|
||||
private lazy var routingSubscription: RoutingSubscription = {
|
||||
RoutingSubscription(parent: self)
|
||||
}()
|
||||
|
||||
/// Creates a publisher that repeatedly emits the current date
|
||||
/// on the given interval.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The interval on which to publish events.
|
||||
/// - tolerance: The allowed timing variance when emitting events.
|
||||
/// Defaults to `nil`, which allows any variance.
|
||||
/// - runLoop: The run loop on which the timer runs.
|
||||
/// - mode: The run loop mode in which to run the timer.
|
||||
/// - options: Scheduler options passed to the timer. Defaults to `nil`.
|
||||
public init(
|
||||
interval: TimeInterval,
|
||||
tolerance: TimeInterval? = nil,
|
||||
runLoop: RunLoop,
|
||||
mode: RunLoop.Mode,
|
||||
options: RunLoop.OCombine.SchedulerOptions? = nil
|
||||
) {
|
||||
self.interval = interval
|
||||
self.tolerance = tolerance
|
||||
self.runLoop = runLoop
|
||||
self.mode = mode
|
||||
self.options = options
|
||||
}
|
||||
|
||||
/// Adapter subscription to allow `Timer` to multiplex to multiple subscribers
|
||||
/// the values produced by a single `TimerPublisher.Inner`
|
||||
private final class RoutingSubscription
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Date
|
||||
typealias Failure = Never
|
||||
|
||||
private typealias ErasedSubscriber = AnySubscriber<Output, Failure>
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
// Inner is IUP due to init requirements
|
||||
// swiftlint:disable:next implicitly_unwrapped_optional
|
||||
private var inner: Inner!
|
||||
|
||||
private var subscribers: [ErasedSubscriber] = []
|
||||
|
||||
private var isConnected = false
|
||||
|
||||
init(parent: TimerPublisher) {
|
||||
inner = Inner(parent: parent, downstream: self)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func addSubscriber<Downstream: Subscriber>(_ downstream: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
lock.lock()
|
||||
subscribers.append(AnySubscriber(downstream))
|
||||
lock.unlock()
|
||||
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ value: Input) -> Subscribers.Demand {
|
||||
var resultingDemand = Subscribers.Demand.none
|
||||
lock.lock()
|
||||
let subscribers = self.subscribers
|
||||
let isConnected = self.isConnected
|
||||
lock.unlock()
|
||||
|
||||
guard isConnected else {
|
||||
// This branch is only reachable in case of a race condition.
|
||||
return .none
|
||||
}
|
||||
|
||||
for subscriber in subscribers {
|
||||
resultingDemand += subscriber.receive(value)
|
||||
}
|
||||
return resultingDemand
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let inner = self.inner!
|
||||
lock.unlock()
|
||||
|
||||
inner.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let inner = self.inner!
|
||||
isConnected = false
|
||||
subscribers = []
|
||||
lock.unlock()
|
||||
|
||||
inner.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Timer" }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
func startPublishing() {
|
||||
lock.lock()
|
||||
let isConnected = self.isConnected
|
||||
self.isConnected = true
|
||||
let inner = self.inner!
|
||||
lock.unlock()
|
||||
if isConnected { return }
|
||||
inner.startPublishing()
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
routingSubscription.addSubscriber(subscriber)
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
routingSubscription.startPublishing()
|
||||
return routingSubscription
|
||||
}
|
||||
|
||||
private typealias Parent = TimerPublisher
|
||||
|
||||
private final class Inner
|
||||
: NSObject,
|
||||
Subscription,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private lazy var timer: CFRunLoopTimer? = {
|
||||
let timer = CFRunLoopTimerCreateWithHandler(
|
||||
nil,
|
||||
Date().timeIntervalSinceReferenceDate,
|
||||
parent?.interval ?? 0,
|
||||
0,
|
||||
0,
|
||||
{ [weak self] _ in self?.timerFired() }
|
||||
)!
|
||||
CFRunLoopTimerSetTolerance(timer, parent?.tolerance ?? 0)
|
||||
return timer
|
||||
}()
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstream: RoutingSubscription?
|
||||
|
||||
private var parent: Parent?
|
||||
|
||||
private var started = false
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
init(parent: Parent, downstream: RoutingSubscription) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func startPublishing() {
|
||||
lock.lock()
|
||||
guard let timer = self.timer,
|
||||
let parent = self.parent,
|
||||
!started else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
started = true
|
||||
lock.unlock()
|
||||
|
||||
CFRunLoopAddTimer(parent.runLoop.getCFRunLoop(),
|
||||
timer,
|
||||
parent.mode.asCFRunLoopMode())
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard let timer = self.timer else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
downstream = nil
|
||||
parent = nil
|
||||
started = false
|
||||
demand = .none
|
||||
self.timer = nil
|
||||
lock.unlock()
|
||||
|
||||
CFRunLoopTimerInvalidate(timer)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
guard parent != nil else {
|
||||
return
|
||||
}
|
||||
self.demand += demand
|
||||
}
|
||||
|
||||
override var description: String { return "Timer" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream as Any),
|
||||
("interval", parent?.interval as Any),
|
||||
("tolerance", parent?.tolerance as Any),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
private func timerFired() {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream,
|
||||
parent != nil,
|
||||
demand > 0
|
||||
else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
|
||||
let newDemand = downstream.receive(Date())
|
||||
guard newDemand > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
extension Timer {
|
||||
|
||||
/// A publisher that repeatedly emits the current date on a given interval.
|
||||
public typealias TimerPublisher = OCombine.TimerPublisher
|
||||
}
|
||||
#endif
|
||||
|
||||
extension RunLoop.Mode {
|
||||
fileprivate func asCFRunLoopMode() -> CFRunLoopMode {
|
||||
#if canImport(Darwin)
|
||||
return CFRunLoopMode(rawValue as CFString)
|
||||
#else
|
||||
return rawValue.withCString {
|
||||
#if swift(>=5.3)
|
||||
let encoding = CFStringBuiltInEncodings.UTF8.rawValue
|
||||
#else
|
||||
let encoding = CFStringEncoding(kCFStringEncodingUTF8)
|
||||
#endif // swift(>=5.3)
|
||||
|
||||
return CFStringCreateWithCString(
|
||||
nil,
|
||||
$0,
|
||||
encoding
|
||||
)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -102,36 +102,6 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
|
||||
}
|
||||
|
||||
func testRequestSeveralTimes() throws {
|
||||
let cvs = Sut(-1)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
cvs.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CurrentValueSubject")])
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(2))
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(1))
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CurrentValueSubject"),
|
||||
.value(-1)])
|
||||
|
||||
for i in 0 ..< 10 {
|
||||
cvs.send(i)
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CurrentValueSubject"),
|
||||
.value(-1),
|
||||
.value(0),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4)])
|
||||
}
|
||||
|
||||
func testCrashOnZeroInitialDemand() {
|
||||
assertCrashes {
|
||||
let subscriber = TrackingSubscriber(
|
||||
@@ -169,20 +139,6 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
func testChangeValueAfterCompletion() {
|
||||
let cvs = Sut(0)
|
||||
cvs.send(completion: .finished)
|
||||
cvs.value = 42
|
||||
XCTAssertEqual(cvs.value, 42)
|
||||
}
|
||||
|
||||
func testSendValueAfterCompletion() {
|
||||
let cvs = Sut(0)
|
||||
cvs.send(completion: .finished)
|
||||
cvs.send(42)
|
||||
XCTAssertEqual(cvs.value, 0)
|
||||
}
|
||||
|
||||
func testMultipleSubscriptions() {
|
||||
|
||||
let cvs = Sut(112)
|
||||
@@ -268,42 +224,6 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject")])
|
||||
|
||||
for (i, subscription) in subscriber.tracking.subscriptions.enumerated()
|
||||
where i.isMultiple(of: 2)
|
||||
{
|
||||
subscription.cancel()
|
||||
}
|
||||
cvs.value = 200
|
||||
|
||||
XCTAssertEqual(subscriber.tracking.history,
|
||||
[.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(200),
|
||||
.value(200),
|
||||
.value(200),
|
||||
.value(200),
|
||||
.value(200)])
|
||||
}
|
||||
|
||||
// Reactive Streams Spec: Rule #6
|
||||
@@ -455,100 +375,6 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testCompletion() throws {
|
||||
let passthrough = Sut(42)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
|
||||
passthrough.subscribe(tracking)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(12))
|
||||
|
||||
passthrough.send(1)
|
||||
|
||||
expectedChildren(
|
||||
("parent", .contains("CurrentValueSubject")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(10)"),
|
||||
("subject", .contains("CurrentValueSubject"))
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
|
||||
passthrough.send(completion: .finished)
|
||||
|
||||
if !hasCustomMirrorUseAfterFreeBug {
|
||||
expectedChildren(
|
||||
("parent", "nil"),
|
||||
("downstream", "nil"),
|
||||
("demand", "max(10)"),
|
||||
("subject", "nil")
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CurrentValueSubject"),
|
||||
.value(42),
|
||||
.value(1),
|
||||
.completion(.finished)])
|
||||
|
||||
passthrough.send(completion: .failure(.oops))
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
|
||||
if !hasCustomMirrorUseAfterFreeBug {
|
||||
expectedChildren(
|
||||
("parent", "nil"),
|
||||
("downstream", "nil"),
|
||||
("demand", "max(10)"),
|
||||
("subject", "nil")
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CurrentValueSubject"),
|
||||
.value(42),
|
||||
.value(1),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testCancellation() throws {
|
||||
let cvs = Sut(42)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
|
||||
cvs.subscribe(tracking)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(12))
|
||||
|
||||
cvs.send(1)
|
||||
|
||||
expectedChildren(
|
||||
("parent", .contains("CurrentValueSubject")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(10)"),
|
||||
("subject", .contains("CurrentValueSubject"))
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(4))
|
||||
|
||||
if !hasCustomMirrorUseAfterFreeBug {
|
||||
expectedChildren(
|
||||
("parent", "nil"),
|
||||
("downstream", "nil"),
|
||||
("demand", "max(10)"),
|
||||
("subject", "nil")
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CurrentValueSubject"),
|
||||
.value(42),
|
||||
.value(1)])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
@@ -599,87 +425,68 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
|
||||
func testCancelsUpstreamSubscriptionsOnDeinit() {
|
||||
let subscription = CustomSubscription()
|
||||
do {
|
||||
let cvs = Sut(42)
|
||||
for _ in 0 ..< 5 {
|
||||
cvs.send(subscription: subscription)
|
||||
func testSynchronization() {
|
||||
|
||||
let subscriptions = Atomic<[Subscription]>([])
|
||||
let inputs = Atomic<[Int]>([])
|
||||
let completions = Atomic<[Subscribers.Completion<TestingError>]>([])
|
||||
|
||||
let cvs = Sut(112)
|
||||
let subscriber = AnySubscriber<Int, TestingError>(
|
||||
receiveSubscription: { subscription in
|
||||
subscriptions.do { $0.append(subscription) }
|
||||
subscription.request(.unlimited)
|
||||
},
|
||||
receiveValue: { value in
|
||||
inputs.do { $0.append(value) }
|
||||
return .none
|
||||
},
|
||||
receiveCompletion: { completion in
|
||||
completions.do { $0.append(completion) }
|
||||
}
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
}
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.requested(.unlimited),
|
||||
.cancelled,
|
||||
.cancelled,
|
||||
.cancelled,
|
||||
.cancelled,
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testReleasesEverythingOnTermination() {
|
||||
|
||||
enum TerminationReason: CaseIterable {
|
||||
case cancelled
|
||||
case finished
|
||||
case failed
|
||||
}
|
||||
|
||||
for reason in TerminationReason.allCases {
|
||||
weak var weakSubscriber: TrackingSubscriber?
|
||||
weak var weakSubject: Sut?
|
||||
weak var weakSubscription: AnyObject?
|
||||
|
||||
do {
|
||||
let subject = Sut(42)
|
||||
do {
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
weakSubscription = $0 as AnyObject
|
||||
}
|
||||
)
|
||||
weakSubscriber = subscriber
|
||||
weakSubject = subject
|
||||
|
||||
subject.subscribe(subscriber)
|
||||
}
|
||||
|
||||
switch reason {
|
||||
case .cancelled:
|
||||
(weakSubscription as? Subscription)?.cancel()
|
||||
case .finished:
|
||||
subject.send(completion: .finished)
|
||||
case .failed:
|
||||
subject.send(completion: .failure(.oops))
|
||||
}
|
||||
|
||||
XCTAssertNil(weakSubscriber, "Subscriber leaked - \(reason)")
|
||||
XCTAssertNil(weakSubscription, "Subscription leaked - \(reason)")
|
||||
}
|
||||
|
||||
XCTAssertNil(weakSubject, "Subject leaked - \(reason)")
|
||||
}
|
||||
}
|
||||
|
||||
func testConduitReflection() throws {
|
||||
try testSubscriptionReflection(
|
||||
description: "CurrentValueSubject",
|
||||
customMirror: expectedChildren(
|
||||
("parent", .contains("CurrentValueSubject")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(0)"),
|
||||
("subject", .contains("CurrentValueSubject"))
|
||||
),
|
||||
playgroundDescription: "CurrentValueSubject",
|
||||
sut: CurrentValueSubject<Int, Error>(42)
|
||||
)
|
||||
|
||||
race(
|
||||
{
|
||||
cvs.subscribe(subscriber)
|
||||
},
|
||||
{
|
||||
cvs.subscribe(subscriber)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(subscriptions.value.count, 200)
|
||||
|
||||
race(
|
||||
{
|
||||
cvs.value = 42
|
||||
},
|
||||
{
|
||||
cvs.value = 42
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(inputs.value.count, 40200)
|
||||
XCTAssertEqual(cvs.value, 42)
|
||||
|
||||
race(
|
||||
{
|
||||
subscriptions.value[0].request(.max(4))
|
||||
},
|
||||
{
|
||||
subscriptions.value[0].request(.max(10))
|
||||
}
|
||||
)
|
||||
|
||||
race(
|
||||
{
|
||||
cvs.send(completion: .finished)
|
||||
},
|
||||
{
|
||||
cvs.send(completion: .failure(""))
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(completions.value.count, 200)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,453 +0,0 @@
|
||||
//
|
||||
// OperationQueueSchedulerTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 14.06.2020.
|
||||
//
|
||||
|
||||
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 OperationQueueSchedulerTests: XCTestCase {
|
||||
|
||||
// MARK: - Scheduler.SchedulerTimeType
|
||||
|
||||
func testSchedulerTimeTypeDistance() {
|
||||
RunLoopSchedulerTests.testSchedulerTimeTypeDistance(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeAdvanced() {
|
||||
RunLoopSchedulerTests.testSchedulerTimeTypeAdvanced(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeEquatable() {
|
||||
RunLoopSchedulerTests.testSchedulerTimeTypeEquatable(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeCodable() throws {
|
||||
try RunLoopSchedulerTests
|
||||
.testSchedulerTimeTypeCodable(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
// MARK: - Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
func testStrideToTimeInterval() {
|
||||
RunLoopSchedulerTests.testStrideToTimeInterval(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideFromTimeInterval() {
|
||||
RunLoopSchedulerTests.testStrideFromTimeInterval(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideFromNumericValue() {
|
||||
RunLoopSchedulerTests.testStrideFromNumericValue(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideComparable() {
|
||||
RunLoopSchedulerTests.testStrideComparable(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideMultiplication() {
|
||||
RunLoopSchedulerTests.testStrideMultiplication(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideAddition() {
|
||||
RunLoopSchedulerTests.testStrideAddition(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideSubtraction() {
|
||||
RunLoopSchedulerTests.testStrideSubtraction(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
func testStrideCodable() throws {
|
||||
try RunLoopSchedulerTests.testStrideCodable(OperationQueueScheduler.self)
|
||||
}
|
||||
|
||||
// MARK: - Scheduler
|
||||
|
||||
#if canImport(Darwin)
|
||||
// FIXME: These tests crash with swift-corelibs-foundation.
|
||||
// The issue has been resolved in
|
||||
// https://github.com/apple/swift-corelibs-foundation/pull/2779
|
||||
// but it hasn't made it into an official release yet.
|
||||
|
||||
func testScheduleActionOnceNowWithTestQueue() {
|
||||
let queue = TestOperationQueue()
|
||||
let scheduler = makeScheduler(queue)
|
||||
|
||||
let counter = Atomic(0)
|
||||
scheduler.schedule {
|
||||
counter.do { $0 += 1 }
|
||||
}
|
||||
|
||||
XCTAssertEqual(queue.history.count, 1)
|
||||
|
||||
guard case let .addOperation(op as BlockOperation)? = queue.history.first else {
|
||||
XCTFail("Unexpected history")
|
||||
return
|
||||
}
|
||||
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
|
||||
XCTAssertEqual(counter.value, 1)
|
||||
op.main()
|
||||
XCTAssertEqual(counter.value, 2)
|
||||
op.main()
|
||||
XCTAssertEqual(counter.value, 3)
|
||||
}
|
||||
|
||||
func testScheduleActionOnceNowWithRealQueue() {
|
||||
let mainQueue = OperationQueue.main
|
||||
let now = Date()
|
||||
var actualDate = Date.distantPast
|
||||
executeOnBackgroundThread {
|
||||
makeScheduler(mainQueue).schedule {
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
actualDate = Date()
|
||||
XCTAssertNotNil(OperationQueue.current)
|
||||
RunLoop.current.run(until: Date() + 0.01)
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(actualDate, .distantPast)
|
||||
RunLoop.main.run(until: Date() + 0.05)
|
||||
XCTAssertEqual(actualDate.timeIntervalSinceReferenceDate,
|
||||
now.timeIntervalSinceReferenceDate,
|
||||
accuracy: 0.1)
|
||||
}
|
||||
|
||||
func testScheduleActionOnceLaterWithTestQueue() {
|
||||
let queue = TestOperationQueue()
|
||||
let scheduler = makeScheduler(queue)
|
||||
let desiredDelay: TimeInterval = 0.6
|
||||
|
||||
let counter = Atomic(0)
|
||||
scheduler.schedule(after: scheduler.now.advanced(by: .init(desiredDelay))) {
|
||||
counter.do { $0 += 1 }
|
||||
}
|
||||
|
||||
XCTAssertEqual(queue.history.count, 1)
|
||||
|
||||
guard case let .addOperation(op)? = queue.history.first else {
|
||||
XCTFail("Unexpected history")
|
||||
return
|
||||
}
|
||||
XCTAssertFalse(op is BlockOperation)
|
||||
XCTAssertFalse(op.isReady)
|
||||
XCTAssertFalse(op.isFinished)
|
||||
XCTAssertFalse(op.isCancelled)
|
||||
XCTAssertFalse(op.isAsynchronous)
|
||||
XCTAssertFalse(op.isConcurrent)
|
||||
XCTAssert(op is Cancellable)
|
||||
|
||||
XCTAssertEqual(counter.value, 0)
|
||||
let now = Date()
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
XCTAssertEqual(counter.value, 1)
|
||||
XCTAssertEqual(Date().timeIntervalSinceReferenceDate,
|
||||
(now + desiredDelay).timeIntervalSinceReferenceDate,
|
||||
accuracy: desiredDelay / 3)
|
||||
|
||||
assertCrashes {
|
||||
op.main()
|
||||
}
|
||||
}
|
||||
|
||||
func testScheduleActionOnceLaterWithRealQueue() {
|
||||
let mainQueue = OperationQueue.main
|
||||
let startDate = Date()
|
||||
var actualDate = Date.distantPast
|
||||
let desiredDelay: TimeInterval = 2
|
||||
executeOnBackgroundThread {
|
||||
let scheduler = makeScheduler(mainQueue)
|
||||
scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: .init(desiredDelay))) {
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
actualDate = Date()
|
||||
XCTAssertNotNil(OperationQueue.current)
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(actualDate, .distantPast)
|
||||
RunLoop.main.run(until: Date() + desiredDelay * 2)
|
||||
XCTAssertEqual(
|
||||
actualDate.timeIntervalSinceReferenceDate -
|
||||
startDate.timeIntervalSinceReferenceDate,
|
||||
desiredDelay,
|
||||
accuracy: desiredDelay / 3
|
||||
)
|
||||
}
|
||||
|
||||
func testScheduleRepeatingWithTestQueue() {
|
||||
let queue = TestOperationQueue()
|
||||
let scheduler = makeScheduler(queue)
|
||||
let desiredDelay: TimeInterval = 0.7
|
||||
let desiredInterval: TimeInterval = 0.3
|
||||
|
||||
let counter = Atomic(0)
|
||||
let cancellable = scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: .init(desiredDelay)),
|
||||
interval: .init(desiredInterval)) {
|
||||
counter.do { $0 += 1 }
|
||||
}
|
||||
|
||||
XCTAssertEqual(queue.history.count, 1)
|
||||
|
||||
guard case let .addOperation(op)? = queue.history.first else {
|
||||
XCTFail("Unexpected history")
|
||||
return
|
||||
}
|
||||
XCTAssertFalse(op is BlockOperation)
|
||||
XCTAssertFalse(op.isReady)
|
||||
XCTAssertFalse(op.isFinished)
|
||||
XCTAssertFalse(op.isCancelled)
|
||||
XCTAssertFalse(op.isAsynchronous)
|
||||
XCTAssertFalse(op.isConcurrent)
|
||||
XCTAssert(op is Cancellable)
|
||||
XCTAssert(cancellable is AnyCancellable)
|
||||
|
||||
XCTAssertEqual(counter.value, 0)
|
||||
let now = Date()
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
XCTAssertEqual(counter.value, 1)
|
||||
let expectedDelay = desiredDelay + desiredInterval
|
||||
XCTAssertEqual(Date().timeIntervalSinceReferenceDate,
|
||||
(now + expectedDelay).timeIntervalSinceReferenceDate,
|
||||
accuracy: expectedDelay / 3)
|
||||
|
||||
assertCrashes {
|
||||
op.main()
|
||||
}
|
||||
}
|
||||
|
||||
func testScheduleRepeatingWithRealQueue() {
|
||||
let mainQueue = OperationQueue.main
|
||||
let startDate = Date()
|
||||
|
||||
let desiredDelay: TimeInterval = 0.7
|
||||
let desiredInterval: TimeInterval = 0.3
|
||||
|
||||
let ticks = Atomic([TimeInterval]())
|
||||
|
||||
let cancellable = executeOnBackgroundThread { () -> Cancellable in
|
||||
let scheduler = makeScheduler(mainQueue)
|
||||
return scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: .init(desiredDelay)),
|
||||
interval: .init(desiredInterval)) {
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
ticks.do { $0.append(Date().timeIntervalSinceReferenceDate) }
|
||||
XCTAssertNotNil(OperationQueue.current)
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssert(cancellable is AnyCancellable)
|
||||
|
||||
XCTAssertEqual(ticks.value.count, 0)
|
||||
RunLoop.main.run(until: Date() + 0.001)
|
||||
XCTAssertEqual(ticks.value.count, 0)
|
||||
|
||||
// The OperationQueue scheduler doesn't repeat actions.
|
||||
// Wait some extra time to make sure this is the case.
|
||||
RunLoop.main.run(until: Date() + desiredDelay + desiredInterval * 5)
|
||||
|
||||
if ticks.value.isEmpty {
|
||||
XCTFail("The scheduler doesn't work")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(ticks.value.count, 1)
|
||||
|
||||
let expectedDelay = desiredDelay + desiredInterval
|
||||
XCTAssertEqual(
|
||||
ticks.value[0],
|
||||
(startDate + expectedDelay).timeIntervalSinceReferenceDate,
|
||||
accuracy: expectedDelay / 3
|
||||
)
|
||||
}
|
||||
#endif // canImport(Darwin)
|
||||
|
||||
func testMinimumTolerance() {
|
||||
let scheduler = makeScheduler(.main)
|
||||
XCTAssertEqual(scheduler.minimumTolerance, .init(0))
|
||||
}
|
||||
|
||||
func testNow() {
|
||||
let scheduler = makeScheduler(.main)
|
||||
XCTAssertEqual(scheduler.now.date.timeIntervalSinceReferenceDate,
|
||||
Date().timeIntervalSinceReferenceDate,
|
||||
accuracy: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
|
||||
private typealias OperationQueueScheduler = OperationQueue
|
||||
|
||||
private func makeScheduler(_ queue: OperationQueue) -> OperationQueueScheduler {
|
||||
return queue
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
private typealias OperationQueueScheduler = OperationQueue.OCombine
|
||||
|
||||
private func makeScheduler(_ queue: OperationQueue) -> OperationQueueScheduler {
|
||||
return queue.ocombine
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension OperationQueueScheduler.SchedulerTimeType.Stride
|
||||
: TimeIntervalBackedSchedulerStride
|
||||
{}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension OperationQueueScheduler.SchedulerTimeType: DateBackedSchedulerTimeType {}
|
||||
|
||||
extension OperationQueueScheduler: RunLoopLikeScheduler {}
|
||||
|
||||
private final class TestOperationQueue: OperationQueue {
|
||||
|
||||
enum Event {
|
||||
case progress
|
||||
case addOperation(Operation)
|
||||
case addOperations([Operation], waitUntilFinished: Bool)
|
||||
case addBlockOperation(() -> Void)
|
||||
case addBarrierBlock(() -> Void)
|
||||
case getMaxConcurrentOperationCount
|
||||
case setMaxConcurrentOperationCount(Int)
|
||||
case getIsSuspended
|
||||
case setIsSuspended(Bool)
|
||||
case getName
|
||||
case setName(String?)
|
||||
case getQualityOfService
|
||||
case setQualityOfService(QualityOfService)
|
||||
case getUnderlyingQueue
|
||||
case setUnderlyingQueue(DispatchQueue?)
|
||||
case cancelAllOperations
|
||||
case waitUntilAllOperationsAreFinished
|
||||
case operations
|
||||
case operationCount
|
||||
}
|
||||
|
||||
private(set) var history = [Event]()
|
||||
|
||||
#if swift(>=5.1)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
override var progress: Progress {
|
||||
history.append(.progress)
|
||||
return super.progress
|
||||
}
|
||||
#endif // swift(>=5.1)
|
||||
|
||||
override func addOperation(_ op: Operation) {
|
||||
history.append(.addOperation(op))
|
||||
super.addOperation(op)
|
||||
}
|
||||
|
||||
override func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) {
|
||||
history.append(.addOperations(ops, waitUntilFinished: wait))
|
||||
super.addOperations(ops, waitUntilFinished: wait)
|
||||
}
|
||||
|
||||
override func addOperation(_ block: @escaping () -> Void) {
|
||||
history.append(.addBlockOperation(block))
|
||||
super.addOperation(block)
|
||||
}
|
||||
|
||||
#if swift(>=5.1)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
override func addBarrierBlock(_ barrier: @escaping () -> Void) {
|
||||
history.append(.addBarrierBlock(barrier))
|
||||
super.addBarrierBlock(barrier)
|
||||
}
|
||||
#endif // swift(>=5.1)
|
||||
|
||||
override var maxConcurrentOperationCount: Int {
|
||||
get {
|
||||
history.append(.getMaxConcurrentOperationCount)
|
||||
return super.maxConcurrentOperationCount
|
||||
}
|
||||
set {
|
||||
history.append(.setMaxConcurrentOperationCount(newValue))
|
||||
super.maxConcurrentOperationCount = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override var isSuspended: Bool {
|
||||
get {
|
||||
history.append(.getIsSuspended)
|
||||
return super.isSuspended
|
||||
}
|
||||
set {
|
||||
history.append(.setIsSuspended(newValue))
|
||||
super.isSuspended = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override var name: String? {
|
||||
get {
|
||||
history.append(.getName)
|
||||
return super.name
|
||||
}
|
||||
set {
|
||||
history.append(.setName(newValue))
|
||||
super.name = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override var qualityOfService: QualityOfService {
|
||||
get {
|
||||
history.append(.getQualityOfService)
|
||||
return super.qualityOfService
|
||||
}
|
||||
set {
|
||||
history.append(.setQualityOfService(newValue))
|
||||
super.qualityOfService = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override var underlyingQueue: DispatchQueue? {
|
||||
get {
|
||||
history.append(.getUnderlyingQueue)
|
||||
return super.underlyingQueue
|
||||
}
|
||||
set {
|
||||
history.append(.setUnderlyingQueue(newValue))
|
||||
super.underlyingQueue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override func cancelAllOperations() {
|
||||
history.append(.cancelAllOperations)
|
||||
super.cancelAllOperations()
|
||||
}
|
||||
|
||||
override func waitUntilAllOperationsAreFinished() {
|
||||
history.append(.waitUntilAllOperationsAreFinished)
|
||||
super.waitUntilAllOperationsAreFinished()
|
||||
}
|
||||
|
||||
// These properties are declared in an extension in swift-corelibs-foundation,
|
||||
// so they can't be overridden.
|
||||
#if canImport(Darwin)
|
||||
override var operations: [Operation] {
|
||||
history.append(.operations)
|
||||
return super.operations
|
||||
}
|
||||
|
||||
override var operationCount: Int {
|
||||
history.append(.operationCount)
|
||||
return super.operationCount
|
||||
}
|
||||
#endif // canImport(Darwin)
|
||||
}
|
||||
@@ -1,633 +0,0 @@
|
||||
//
|
||||
// RunLoopSchedulerTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 14.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 RunLoopSchedulerTests: XCTestCase {
|
||||
|
||||
// MARK: - Scheduler.SchedulerTimeType
|
||||
|
||||
func testSchedulerTimeTypeDistance() {
|
||||
RunLoopSchedulerTests.testSchedulerTimeTypeDistance(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testSchedulerTimeTypeDistance<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
let time1 = Context.SchedulerTimeType(Date(timeIntervalSince1970: 10_000))
|
||||
let time2 = Context.SchedulerTimeType(Date(timeIntervalSince1970: 10_431))
|
||||
let distantFuture = Context.SchedulerTimeType(.distantFuture)
|
||||
let notSoDistantFuture = Context.SchedulerTimeType(
|
||||
Date.distantFuture - 1024
|
||||
)
|
||||
|
||||
XCTAssertEqual(time1.distance(to: time2).timeInterval, 431)
|
||||
XCTAssertEqual(time2.distance(to: time1).timeInterval, -431)
|
||||
|
||||
XCTAssertEqual(time1.distance(to: distantFuture).timeInterval, 64_092_201_200)
|
||||
XCTAssertEqual(distantFuture.distance(to: time1).timeInterval, -64_092_201_200)
|
||||
XCTAssertEqual(time2.distance(to: distantFuture).timeInterval, 64_092_200_769)
|
||||
XCTAssertEqual(distantFuture.distance(to: time2).timeInterval, -64_092_200_769)
|
||||
|
||||
XCTAssertEqual(time1.distance(to: notSoDistantFuture).timeInterval,
|
||||
64_092_200_176)
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time1).timeInterval,
|
||||
-64_092_200_176)
|
||||
XCTAssertEqual(time2.distance(to: notSoDistantFuture).timeInterval,
|
||||
64_092_199_745)
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time2).timeInterval,
|
||||
-64_092_199_745)
|
||||
|
||||
XCTAssertEqual(distantFuture.distance(to: distantFuture).timeInterval,
|
||||
0)
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: notSoDistantFuture).timeInterval,
|
||||
0)
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeAdvanced() {
|
||||
RunLoopSchedulerTests.testSchedulerTimeTypeAdvanced(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testSchedulerTimeTypeAdvanced<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
let time =
|
||||
Context.SchedulerTimeType(Date(timeIntervalSinceReferenceDate: 10_000))
|
||||
let beginningOfTime =
|
||||
Context.SchedulerTimeType(Date(timeIntervalSinceReferenceDate: 1))
|
||||
let stride1 = Context.SchedulerTimeType.Stride.seconds(431)
|
||||
let stride2 = Context.SchedulerTimeType.Stride.seconds(-220)
|
||||
|
||||
XCTAssertEqual(time.advanced(by: stride1),
|
||||
.init(Date(timeIntervalSinceReferenceDate: 10431)))
|
||||
|
||||
XCTAssertEqual(time.advanced(by: stride2),
|
||||
.init(Date(timeIntervalSinceReferenceDate: 9780)))
|
||||
|
||||
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
|
||||
// 64-bit platforms
|
||||
XCTAssertEqual(time.advanced(by: .nanoseconds(.max)).date,
|
||||
Date(timeIntervalSinceReferenceDate: 9223382036.854776))
|
||||
|
||||
XCTAssertEqual(time.advanced(by: .seconds(.max)).date,
|
||||
Date(timeIntervalSinceReferenceDate: 9.223372036854786E+18))
|
||||
#elseif arch(i386) || arch(arm)
|
||||
// 32-bit platforms
|
||||
XCTAssertEqual(time.advanced(by: .nanoseconds(.max)).date,
|
||||
Date(timeIntervalSinceReferenceDate: 10002.147483647))
|
||||
|
||||
XCTAssertEqual(time.advanced(by: .seconds(.max)).date,
|
||||
Date(timeIntervalSinceReferenceDate: 2147493647))
|
||||
#else
|
||||
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.")
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(beginningOfTime.advanced(by: .nanoseconds(-1000)).date,
|
||||
Date(timeIntervalSinceReferenceDate: 0.999999))
|
||||
|
||||
XCTAssertEqual(beginningOfTime.advanced(by: .seconds(-1000)).date,
|
||||
Date(timeIntervalSinceReferenceDate: -999.0))
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeEquatable() {
|
||||
RunLoopSchedulerTests.testSchedulerTimeTypeEquatable(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testSchedulerTimeTypeEquatable<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
let time1 = Context.SchedulerTimeType(Date(timeIntervalSinceReferenceDate: 10000))
|
||||
let time2 = Context.SchedulerTimeType(Date(timeIntervalSinceReferenceDate: 10000))
|
||||
let time3 = Context.SchedulerTimeType(Date(timeIntervalSinceReferenceDate: 10001))
|
||||
|
||||
XCTAssertEqual(time1, time1)
|
||||
XCTAssertEqual(time2, time2)
|
||||
XCTAssertEqual(time3, time3)
|
||||
|
||||
XCTAssertEqual(time1, time2)
|
||||
XCTAssertEqual(time2, time1)
|
||||
XCTAssertNotEqual(time1, time3)
|
||||
XCTAssertNotEqual(time3, time1)
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeCodable() throws {
|
||||
try RunLoopSchedulerTests.testSchedulerTimeTypeCodable(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testSchedulerTimeTypeCodable<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) throws {
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let time =
|
||||
Context.SchedulerTimeType(Date(timeIntervalSinceReferenceDate: 1024.75))
|
||||
let encodedData = try encoder
|
||||
.encode(time)
|
||||
let encodedString = String(decoding: encodedData, as: UTF8.self)
|
||||
|
||||
XCTAssertEqual(encodedString,
|
||||
#"{"date":1024.75}"#)
|
||||
|
||||
let decodedTime = try decoder
|
||||
.decode(Context.SchedulerTimeType.self, from: encodedData)
|
||||
|
||||
XCTAssertEqual(decodedTime, time)
|
||||
}
|
||||
|
||||
// MARK: - Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
func testStrideToTimeInterval() {
|
||||
RunLoopSchedulerTests.testStrideToTimeInterval(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideToTimeInterval<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
XCTAssertEqual(Stride.seconds(2).timeInterval, 2)
|
||||
XCTAssertEqual(Stride.seconds(2.2).timeInterval, 2.2)
|
||||
XCTAssertEqual(Stride.seconds(Double.infinity).timeInterval, .infinity)
|
||||
XCTAssertEqual(Stride.milliseconds(2).timeInterval, 0.002)
|
||||
XCTAssertEqual(Stride.microseconds(2).timeInterval, 2E-06)
|
||||
XCTAssertEqual(Stride.nanoseconds(2).timeInterval, 2E-09)
|
||||
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
|
||||
// 64-bit platforms
|
||||
XCTAssertEqual(Stride.seconds(Int.max).timeInterval, 9.223372036854776E+18)
|
||||
XCTAssertEqual(Stride.milliseconds(.max).timeInterval, 9.223372036854776E+15)
|
||||
XCTAssertEqual(Stride.microseconds(.max).timeInterval, 9223372036854.775)
|
||||
XCTAssertEqual(Stride.nanoseconds(.max).timeInterval, 9223372036.854776)
|
||||
#elseif arch(i386) || arch(arm)
|
||||
// 32-bit platforms
|
||||
XCTAssertEqual(Stride.seconds(Int.max).timeInterval, 2147483647)
|
||||
XCTAssertEqual(Stride.milliseconds(.max).timeInterval, 2147483.647)
|
||||
XCTAssertEqual(Stride.microseconds(.max).timeInterval, 2147.483647)
|
||||
XCTAssertEqual(Stride.nanoseconds(.max).timeInterval, 2.147483647)
|
||||
#else
|
||||
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.")
|
||||
#endif
|
||||
}
|
||||
|
||||
func testStrideFromTimeInterval() {
|
||||
RunLoopSchedulerTests.testStrideFromTimeInterval(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideFromTimeInterval<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
XCTAssertEqual(Stride(2).magnitude, 2)
|
||||
XCTAssertEqual(Stride(2.2).magnitude, 2.2)
|
||||
XCTAssertEqual(Stride(.infinity).magnitude, .infinity)
|
||||
XCTAssertEqual(Stride(0.002).magnitude, 0.002)
|
||||
XCTAssertEqual(Stride(2E-06).magnitude, 2E-06)
|
||||
XCTAssertEqual(Stride(2E-09).magnitude, 2E-09)
|
||||
XCTAssertEqual(Stride(9.223372036854776E+18).magnitude, 9.223372036854776E+18)
|
||||
}
|
||||
|
||||
func testStrideFromNumericValue() {
|
||||
RunLoopSchedulerTests.testStrideFromNumericValue(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideFromNumericValue<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((1.2 as Stride).magnitude, 1.2)
|
||||
XCTAssertEqual((2 as Stride).magnitude, 2)
|
||||
|
||||
XCTAssertNil(Stride(exactly: UInt64.max))
|
||||
XCTAssertEqual(Stride(exactly: 871 as UInt64)?.magnitude, 871)
|
||||
}
|
||||
|
||||
func testStrideComparable() {
|
||||
RunLoopSchedulerTests.testStrideComparable(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideComparable<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
XCTAssertLessThan(Stride.nanoseconds(1), .nanoseconds(2))
|
||||
XCTAssertGreaterThan(Stride.nanoseconds(-2), .microseconds(-10))
|
||||
XCTAssertLessThan(Stride.milliseconds(2), .seconds(2))
|
||||
}
|
||||
|
||||
func testStrideMultiplication() {
|
||||
RunLoopSchedulerTests.testStrideMultiplication(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideMultiplication<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.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, 1.8E-17)
|
||||
XCTAssertEqual((Stride.nanoseconds(18) * .microseconds(1)).magnitude, 1.8E-14)
|
||||
XCTAssertEqual((Stride.nanoseconds(1) * .nanoseconds(18)).magnitude, 1.8E-17)
|
||||
XCTAssertEqual((Stride.microseconds(1) * .nanoseconds(18)).magnitude, 1.8E-14)
|
||||
XCTAssertEqual((Stride.nanoseconds(15) * .nanoseconds(2)).magnitude, 3E-17)
|
||||
XCTAssertEqual((Stride.microseconds(-3) * .nanoseconds(10)).magnitude, -3E-14)
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(0)
|
||||
stride *= .nanoseconds(61346)
|
||||
XCTAssertEqual(stride.magnitude, 0)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(61346)
|
||||
stride *= .nanoseconds(0)
|
||||
XCTAssertEqual(stride.magnitude, 0)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(18)
|
||||
stride *= .nanoseconds(1)
|
||||
XCTAssertEqual(stride.magnitude, 1.8E-17)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(18)
|
||||
stride *= .microseconds(1)
|
||||
XCTAssertEqual(stride.magnitude, 1.8E-14)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(1)
|
||||
stride *= .nanoseconds(18)
|
||||
XCTAssertEqual(stride.magnitude, 1.8E-17)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.microseconds(1)
|
||||
stride *= .nanoseconds(18)
|
||||
XCTAssertEqual(stride.magnitude, 1.8E-14)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(15)
|
||||
stride *= .nanoseconds(2)
|
||||
XCTAssertEqual(stride.magnitude, 3E-17)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.microseconds(-3)
|
||||
stride *= .nanoseconds(10)
|
||||
XCTAssertEqual(stride.magnitude, -3E-14)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideAddition() {
|
||||
RunLoopSchedulerTests.testStrideAddition(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideAddition<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) + .microseconds(2)).magnitude, 2E-06)
|
||||
XCTAssertEqual((Stride.nanoseconds(2) + .microseconds(0)).magnitude, 2E-09)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) + .nanoseconds(12)).magnitude,
|
||||
1.8999999999999998E-08)
|
||||
XCTAssertEqual((Stride.nanoseconds(12) + .nanoseconds(7)).magnitude,
|
||||
1.8999999999999998E-08)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) + .nanoseconds(-12)).magnitude, -5E-09)
|
||||
XCTAssertEqual((Stride.nanoseconds(-12) + .nanoseconds(7)).magnitude, -5E-09)
|
||||
XCTAssertEqual((Stride.milliseconds(-12) + .seconds(7)).magnitude, 6.988)
|
||||
XCTAssertEqual((Stride.seconds(-12) + .milliseconds(7)).magnitude, -11.993)
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(0)
|
||||
stride += .microseconds(2)
|
||||
XCTAssertEqual(stride.magnitude, 2E-06)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(2)
|
||||
stride += .microseconds(0)
|
||||
XCTAssertEqual(stride.magnitude, 2E-09)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride += .nanoseconds(12)
|
||||
XCTAssertEqual(stride.magnitude, 1.8999999999999998E-08)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(12)
|
||||
stride += .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, 1.8999999999999998E-08)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride += .nanoseconds(-12)
|
||||
XCTAssertEqual(stride.magnitude, -5E-09)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(-12)
|
||||
stride += .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -5E-09)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.seconds(-12)
|
||||
stride += .milliseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -11.993)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.milliseconds(-12)
|
||||
stride += .seconds(7)
|
||||
XCTAssertEqual(stride.magnitude, 6.988)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideSubtraction() {
|
||||
RunLoopSchedulerTests.testStrideSubtraction(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideSubtraction<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) - .microseconds(2)).magnitude, -2E-06)
|
||||
XCTAssertEqual((Stride.nanoseconds(2) - .microseconds(0)).magnitude, 2E-09)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) - .nanoseconds(12)).magnitude, -5E-09)
|
||||
XCTAssertEqual((Stride.nanoseconds(12) - .nanoseconds(7)).magnitude, 5E-09)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) - .nanoseconds(-12)).magnitude,
|
||||
1.8999999999999998E-08)
|
||||
XCTAssertEqual((Stride.nanoseconds(-12) - .nanoseconds(7)).magnitude,
|
||||
-1.8999999999999998E-08)
|
||||
XCTAssertEqual((Stride.seconds(-12) - .milliseconds(7)).magnitude, -12.007)
|
||||
XCTAssertEqual((Stride.milliseconds(-12) - .seconds(7)).magnitude, -7.012)
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(0)
|
||||
stride -= .microseconds(2)
|
||||
XCTAssertEqual(stride.magnitude, -2E-06)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(2)
|
||||
stride -= .microseconds(0)
|
||||
XCTAssertEqual(stride.magnitude, 2E-09)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride -= .nanoseconds(12)
|
||||
XCTAssertEqual(stride.magnitude, -5E-09)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(12)
|
||||
stride -= .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, 5E-09)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride -= .nanoseconds(-12)
|
||||
XCTAssertEqual(stride.magnitude, 1.8999999999999998E-08)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(-12)
|
||||
stride -= .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -1.8999999999999998E-08)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.seconds(-12)
|
||||
stride -= .milliseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -12.007)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.milliseconds(-12)
|
||||
stride -= .seconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -7.012)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideCodable() throws {
|
||||
try RunLoopSchedulerTests.testStrideCodable(RunLoopScheduler.self)
|
||||
}
|
||||
|
||||
static func testStrideCodable<Context: RunLoopLikeScheduler>(
|
||||
_ schedulerType: Context.Type
|
||||
) throws {
|
||||
typealias Stride = Context.SchedulerTimeType.Stride
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let stride = Stride.seconds(1024.5)
|
||||
let encodedData = try encoder.encode(stride)
|
||||
let encodedString = String(decoding: encodedData, as: UTF8.self)
|
||||
|
||||
XCTAssertEqual(encodedString, #"{"magnitude":1024.5}"#)
|
||||
|
||||
let decodedStride = try decoder
|
||||
.decode(Stride.self, from: encodedData)
|
||||
|
||||
XCTAssertEqual(decodedStride, stride)
|
||||
}
|
||||
|
||||
// MARK: - Scheduler
|
||||
|
||||
func testScheduleActionOnceNow() {
|
||||
let mainRunLoop = RunLoop.main
|
||||
let now = Date()
|
||||
var actualDate = Date.distantPast
|
||||
executeOnBackgroundThread {
|
||||
makeScheduler(mainRunLoop).schedule {
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
actualDate = Date()
|
||||
RunLoop.current.run(until: Date() + 0.01)
|
||||
}
|
||||
}
|
||||
XCTAssertEqual(actualDate, .distantPast)
|
||||
mainRunLoop.run(until: Date() + 0.05)
|
||||
XCTAssertEqual(actualDate.timeIntervalSinceReferenceDate,
|
||||
now.timeIntervalSinceReferenceDate,
|
||||
accuracy: 0.1)
|
||||
}
|
||||
|
||||
func testScheduleActionOnceLater() {
|
||||
let mainRunLoop = RunLoop.main
|
||||
let now = Date()
|
||||
var actualDate = Date.distantPast
|
||||
let desiredDelay: TimeInterval = 0.6
|
||||
executeOnBackgroundThread {
|
||||
let scheduler = makeScheduler(mainRunLoop)
|
||||
scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: .init(desiredDelay))) {
|
||||
// This is a bug in Combine! (FB7493579)
|
||||
// This should be XCTAssertTrue. When they fix it, this test will fail
|
||||
// and we'll know to fix our implementation.
|
||||
XCTAssertFalse(Thread.isMainThread)
|
||||
actualDate = Date()
|
||||
}
|
||||
RunLoop.current.run(until: Date() + 1)
|
||||
}
|
||||
|
||||
XCTAssertEqual(
|
||||
actualDate.timeIntervalSinceReferenceDate -
|
||||
now.timeIntervalSinceReferenceDate,
|
||||
desiredDelay,
|
||||
accuracy: desiredDelay / 3
|
||||
)
|
||||
}
|
||||
|
||||
func testScheduleRepeating() {
|
||||
let mainRunLoop = RunLoop.main
|
||||
|
||||
let expectation5ticks = expectation(description: "5 ticks")
|
||||
expectation5ticks.expectedFulfillmentCount = 10
|
||||
|
||||
let startDate = Date().timeIntervalSinceReferenceDate
|
||||
|
||||
let ticks = Atomic([TimeInterval]())
|
||||
|
||||
let desiredDelay: TimeInterval = 0.7
|
||||
let desiredInterval: TimeInterval = 0.3
|
||||
|
||||
let cancellable = executeOnBackgroundThread { () -> Cancellable in
|
||||
let scheduler = makeScheduler(mainRunLoop)
|
||||
return scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: .init(desiredDelay)),
|
||||
interval: .init(desiredInterval)) {
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
ticks.do {
|
||||
$0.append(Date().timeIntervalSinceReferenceDate)
|
||||
}
|
||||
expectation5ticks.fulfill()
|
||||
RunLoop.current.run(until: Date() + 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(ticks.value.count, 0)
|
||||
mainRunLoop.run(until: Date() + 0.001)
|
||||
XCTAssertEqual(ticks.value.count, 0)
|
||||
|
||||
wait(for: [expectation5ticks], timeout: 5)
|
||||
|
||||
if ticks.value.isEmpty {
|
||||
XCTFail("The scheduler doesn't work")
|
||||
return
|
||||
}
|
||||
|
||||
let actualDelay = ticks.value[0] - startDate
|
||||
let actualIntervals = zip(ticks.value.dropFirst(), ticks.value.dropLast()).map(-)
|
||||
let averageInterval = actualIntervals.reduce(0, +) / Double(actualIntervals.count)
|
||||
|
||||
XCTAssertEqual(actualDelay,
|
||||
desiredDelay,
|
||||
accuracy: desiredDelay / 3,
|
||||
"""
|
||||
Actual delay (\(actualDelay)) deviates from desired delay \
|
||||
(\(desiredDelay)) too much
|
||||
""")
|
||||
|
||||
XCTAssertEqual(averageInterval,
|
||||
desiredInterval,
|
||||
accuracy: desiredInterval / 3,
|
||||
"""
|
||||
Actual average interval (\(averageInterval)) deviates from \
|
||||
desired interval (\(desiredInterval)) too much.
|
||||
|
||||
Actual intervals: \(actualIntervals)
|
||||
""")
|
||||
|
||||
cancellable.cancel()
|
||||
let numberOfTicksRightAfterCancellation = ticks.value.count
|
||||
mainRunLoop.run(until: Date() + 1)
|
||||
let numberOfTicksOneSecondAfterCancellation = ticks.value.count
|
||||
XCTAssertEqual(numberOfTicksRightAfterCancellation,
|
||||
numberOfTicksOneSecondAfterCancellation)
|
||||
}
|
||||
|
||||
func testMinimumTolerance() {
|
||||
let scheduler = makeScheduler(.main)
|
||||
XCTAssertEqual(scheduler.minimumTolerance, .init(0))
|
||||
}
|
||||
|
||||
func testNow() {
|
||||
let scheduler = makeScheduler(.main)
|
||||
XCTAssertEqual(scheduler.now.date.timeIntervalSinceReferenceDate,
|
||||
Date().timeIntervalSinceReferenceDate,
|
||||
accuracy: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
|
||||
private typealias RunLoopScheduler = RunLoop
|
||||
|
||||
private func makeScheduler(_ runLoop: RunLoop) -> RunLoopScheduler {
|
||||
return runLoop
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
private typealias RunLoopScheduler = RunLoop.OCombine
|
||||
|
||||
private func makeScheduler(_ runLoop: RunLoop) -> RunLoopScheduler {
|
||||
return runLoop.ocombine
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
protocol DateBackedSchedulerTimeType: Strideable, Codable, Hashable {
|
||||
init(_ date: Date)
|
||||
|
||||
var date: Date { get }
|
||||
}
|
||||
|
||||
protocol TimeIntervalBackedSchedulerStride: SchedulerTimeIntervalConvertible,
|
||||
Comparable,
|
||||
SignedNumeric,
|
||||
ExpressibleByFloatLiteral,
|
||||
Codable
|
||||
where Magnitude == TimeInterval
|
||||
{
|
||||
init(_ timeInterval: TimeInterval)
|
||||
|
||||
var timeInterval: TimeInterval { get }
|
||||
}
|
||||
|
||||
protocol RunLoopLikeScheduler: Scheduler
|
||||
where SchedulerTimeType: DateBackedSchedulerTimeType,
|
||||
SchedulerTimeType.Stride: TimeIntervalBackedSchedulerStride {
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension RunLoopScheduler.SchedulerTimeType.Stride: TimeIntervalBackedSchedulerStride {}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension RunLoopScheduler.SchedulerTimeType: DateBackedSchedulerTimeType {}
|
||||
|
||||
extension RunLoopScheduler: RunLoopLikeScheduler {}
|
||||
@@ -1,224 +0,0 @@
|
||||
//
|
||||
// TimerPublisherTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23.06.2020.
|
||||
//
|
||||
|
||||
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 TimerPublisherTests: XCTestCase {
|
||||
|
||||
func testPublishMethod() {
|
||||
let publisher: TimerPublisher = Timer
|
||||
.publish(every: 0.25,
|
||||
tolerance: 0.02,
|
||||
on: .main,
|
||||
in: RunLoop.Mode(rawValue: "testMode"),
|
||||
options: nil)
|
||||
|
||||
XCTAssertEqual(publisher.interval, 0.25)
|
||||
XCTAssertEqual(publisher.tolerance, nil)
|
||||
XCTAssertEqual(publisher.runLoop, .main)
|
||||
XCTAssertEqual(publisher.mode, RunLoop.Mode(rawValue: "testMode"))
|
||||
XCTAssertNil(publisher.options)
|
||||
}
|
||||
|
||||
func testConnectAndPublish() {
|
||||
let desiredInterval: TimeInterval = 0.5
|
||||
var ticks = [TimeInterval]()
|
||||
|
||||
let tracking1 = TrackingSubscriberBase<Date, Never>(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(3))
|
||||
},
|
||||
receiveValue: {
|
||||
ticks.append($0.timeIntervalSinceReferenceDate)
|
||||
return ticks.count < 3 ? .max(1) : .none
|
||||
}
|
||||
)
|
||||
|
||||
let tracking2 = TrackingSubscriberBase<Date, Never>(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(2))
|
||||
}
|
||||
)
|
||||
let tracking3 = TrackingSubscriberBase<Date, Never>(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(1))
|
||||
},
|
||||
receiveValue: { _ in
|
||||
ticks.count < 3 ? .max(1) : .none
|
||||
}
|
||||
)
|
||||
|
||||
let publisher: TimerPublisher = Timer
|
||||
.publish(every: desiredInterval, on: .main, in: .default)
|
||||
|
||||
publisher.subscribe(tracking1)
|
||||
publisher.subscribe(tracking2)
|
||||
publisher.subscribe(tracking3)
|
||||
|
||||
XCTAssertEqual(tracking1.history, [.subscription("Timer")])
|
||||
|
||||
RunLoop.main.run(until: Date() + 1)
|
||||
|
||||
// Test that no output is produced until we connect
|
||||
XCTAssertEqual(tracking1.history, [.subscription("Timer")])
|
||||
|
||||
let connection = publisher.connect()
|
||||
|
||||
RunLoop.main.run(until: Date() + 10)
|
||||
|
||||
assertCorrectIntervals(ticks: ticks,
|
||||
expectedNumberOfTicks: 10,
|
||||
desiredInterval: desiredInterval)
|
||||
|
||||
let fullHistory =
|
||||
[TrackingSubscriberBase<Date, Never>.Event.subscription("Timer")] +
|
||||
ticks.map { .value(Date(timeIntervalSinceReferenceDate: $0)) }
|
||||
|
||||
connection.cancel()
|
||||
XCTAssert(connection is Subscription)
|
||||
|
||||
RunLoop.main.run(until: Date() + 1)
|
||||
|
||||
XCTAssertEqual(tracking1.history, fullHistory)
|
||||
XCTAssertEqual(tracking2.history, fullHistory)
|
||||
XCTAssertEqual(tracking3.history, fullHistory)
|
||||
}
|
||||
|
||||
func testConnectAndCancelMultipleTimes() throws {
|
||||
let publisher = TimerPublisher(interval: 0.25,
|
||||
runLoop: .main,
|
||||
mode: .default)
|
||||
|
||||
let tracking = TrackingSubscriberBase<Date, Never>()
|
||||
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
let connection1 = publisher.connect()
|
||||
let connection2 = publisher.connect()
|
||||
XCTAssert((connection1 as AnyObject) === (connection2 as AnyObject))
|
||||
|
||||
connection1.cancel()
|
||||
connection1.cancel()
|
||||
|
||||
let connection3 = try XCTUnwrap(publisher.connect() as? Subscription)
|
||||
connection3.request(.max(1))
|
||||
|
||||
RunLoop.main.run(until: Date() + 0.3)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Timer")])
|
||||
}
|
||||
|
||||
func testConnectionReflection() throws {
|
||||
let publisher = TimerPublisher(interval: 0.25,
|
||||
tolerance: 0.4,
|
||||
runLoop: .main,
|
||||
mode: .default,
|
||||
options: nil)
|
||||
|
||||
let connection = publisher.connect()
|
||||
defer { connection.cancel() }
|
||||
|
||||
XCTAssertEqual(
|
||||
(connection as? CustomStringConvertible)?.description,
|
||||
"Timer"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
(connection as? CustomPlaygroundDisplayConvertible)?
|
||||
.playgroundDescription as? String,
|
||||
"Timer"
|
||||
)
|
||||
XCTAssertFalse(connection is CustomDebugStringConvertible)
|
||||
|
||||
let connectionCombineID =
|
||||
try XCTUnwrap(connection as? CustomCombineIdentifierConvertible)
|
||||
.combineIdentifier
|
||||
|
||||
guard let inner = Mirror(reflecting: connection).descendant("some")
|
||||
else {
|
||||
XCTFail("Unexpected representation")
|
||||
return
|
||||
}
|
||||
|
||||
expectedChildren(
|
||||
("downstream", "Optional(Timer)"),
|
||||
("interval", "Optional(0.25)"),
|
||||
("tolerance", "Optional(0.4)")
|
||||
)(Mirror(reflecting: inner))
|
||||
|
||||
connection.cancel()
|
||||
|
||||
expectedChildren(
|
||||
("downstream", "nil"),
|
||||
("interval", "nil"),
|
||||
("tolerance", "nil")
|
||||
)(Mirror(reflecting: inner))
|
||||
|
||||
XCTAssert(inner is NSObject)
|
||||
|
||||
XCTAssertEqual(
|
||||
(inner as? CustomStringConvertible)?.description,
|
||||
"Timer"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
(inner as? CustomPlaygroundDisplayConvertible)?
|
||||
.playgroundDescription as? String,
|
||||
"Timer"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
(inner as? CustomDebugStringConvertible)?.debugDescription,
|
||||
"Timer"
|
||||
)
|
||||
|
||||
let innerCombineID =
|
||||
try XCTUnwrap(inner as? CustomCombineIdentifierConvertible)
|
||||
.combineIdentifier
|
||||
|
||||
XCTAssertEqual(connectionCombineID, innerCombineID)
|
||||
}
|
||||
|
||||
private func assertCorrectIntervals(ticks: [TimeInterval],
|
||||
expectedNumberOfTicks: Int,
|
||||
desiredInterval: TimeInterval,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
XCTAssertEqual(ticks.count, expectedNumberOfTicks, file: file, line: line)
|
||||
|
||||
if ticks.isEmpty { return }
|
||||
|
||||
let actualIntervals = zip(ticks.dropFirst(), ticks.dropLast()).map(-)
|
||||
let averageInterval =
|
||||
actualIntervals.reduce(0, +) / TimeInterval(actualIntervals.count)
|
||||
|
||||
XCTAssertEqual(averageInterval,
|
||||
desiredInterval,
|
||||
accuracy: desiredInterval / 2,
|
||||
"""
|
||||
Actual average interval (\(averageInterval)) deviates from \
|
||||
desired interval (\(desiredInterval)) too much.
|
||||
|
||||
Actual intervals: \(actualIntervals)
|
||||
""",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private typealias TimerPublisher = Timer.TimerPublisher
|
||||
#else
|
||||
private typealias TimerPublisher = Timer.OCombine.TimerPublisher
|
||||
#endif
|
||||
@@ -587,7 +587,7 @@ private final class TestURLSessionDataTask: URLSessionDataTask {
|
||||
#if canImport(Darwin)
|
||||
return super.earliestBeginDate
|
||||
#else
|
||||
return nil // Deprecated in swift-corelibs-foundation
|
||||
return nil // Deprecated in swift-corerlibs-foundation
|
||||
#endif
|
||||
}
|
||||
set {
|
||||
|
||||
@@ -58,7 +58,7 @@ extension XCTest {
|
||||
environment[childProcessEnvVariable] = childProcessEnvVariableOnValue
|
||||
childProcess.environment = environment
|
||||
|
||||
func printDiagnostics() {
|
||||
func printDiagostics() {
|
||||
print("Parent process invocation:")
|
||||
print(ProcessInfo.processInfo.arguments.joined(separator: " "))
|
||||
print("Child process invocation:")
|
||||
@@ -73,13 +73,13 @@ extension XCTest {
|
||||
childProcess.waitUntilExit()
|
||||
if childProcess.terminationReason != .uncaughtSignal {
|
||||
XCTFail("Child process should have crashed: \(childProcess)")
|
||||
printDiagnostics()
|
||||
printDiagostics()
|
||||
}
|
||||
} catch {
|
||||
XCTFail("""
|
||||
Couldn't start child process for testing crash: \(childProcess) - \(error)
|
||||
""")
|
||||
printDiagnostics()
|
||||
printDiagostics()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -220,8 +220,4 @@ func unreachable<T>(_: T) -> Never {
|
||||
fatalError("unreachable")
|
||||
}
|
||||
|
||||
func unreachable() -> Never {
|
||||
fatalError("unreachable")
|
||||
}
|
||||
|
||||
// swiftlint:enable generic_type_name
|
||||
|
||||
@@ -44,16 +44,10 @@ class CustomPublisherBase<Output, Failure: Error>: Publisher, Cancellable {
|
||||
|
||||
var didSubscribe: ((AnySubscriber<Output, Failure>) -> Void)?
|
||||
|
||||
var onDeinit: (() -> Void)?
|
||||
|
||||
required init(subscription: Subscription?) {
|
||||
self.subscription = subscription
|
||||
}
|
||||
|
||||
deinit {
|
||||
onDeinit?()
|
||||
}
|
||||
|
||||
func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
@@ -87,7 +81,7 @@ class CustomPublisherBase<Output, Failure: Error>: Publisher, Cancellable {
|
||||
typealias CustomConnectablePublisher = CustomConnectablePublisherBase<Int, TestingError>
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CustomConnectablePublisherBase<Output, Failure: Error>
|
||||
final class CustomConnectablePublisherBase<Output: Equatable, Failure: Error>
|
||||
: CustomPublisherBase<Output, Failure>,
|
||||
ConnectablePublisher
|
||||
{
|
||||
|
||||
@@ -38,18 +38,11 @@ final class CustomSubscription: Subscription, CustomStringConvertible {
|
||||
|
||||
var onRequest: ((Subscribers.Demand) -> Void)?
|
||||
var onCancel: (() -> Void)?
|
||||
var onDeinit: (() -> Void)?
|
||||
|
||||
init(onRequest: ((Subscribers.Demand) -> Void)? = nil,
|
||||
onCancel: (() -> Void)? = nil,
|
||||
onDeinit: (() -> Void)? = nil) {
|
||||
onCancel: (() -> Void)? = nil) {
|
||||
self.onRequest = onRequest
|
||||
self.onCancel = onCancel
|
||||
self.onDeinit = onDeinit
|
||||
}
|
||||
|
||||
deinit {
|
||||
onDeinit?()
|
||||
}
|
||||
|
||||
var lastRequested: Subscribers.Demand? {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
//
|
||||
// ExecuteOnBackgroundThread.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 04.02.2020.
|
||||
//
|
||||
|
||||
#if canImport(Darwin)
|
||||
import Darwin
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#else
|
||||
#error("How to do threads on this platform?")
|
||||
#endif
|
||||
|
||||
#if canImport(Darwin)
|
||||
private typealias ThreadPtr = UnsafeMutablePointer<pthread_t?>
|
||||
#else
|
||||
private typealias ThreadPtr = UnsafeMutablePointer<pthread_t>
|
||||
#endif
|
||||
|
||||
func executeOnBackgroundThread<ResultType>(
|
||||
_ body: () -> ResultType
|
||||
) -> ResultType {
|
||||
return withoutActuallyEscaping(body) { body in
|
||||
|
||||
// We need this because @convention(c) closures can't capture generic params.
|
||||
var typeErasedBody: () -> UnsafeMutableRawPointer = {
|
||||
let resultPtr = UnsafeMutablePointer<ResultType>.allocate(capacity: 1)
|
||||
resultPtr.initialize(to: body())
|
||||
return UnsafeMutableRawPointer(resultPtr)
|
||||
}
|
||||
|
||||
return withUnsafeMutablePointer(to: &typeErasedBody) { typeErasedBody in
|
||||
let _backgroundThread = ThreadPtr.allocate(capacity: 1)
|
||||
|
||||
defer { _backgroundThread.deallocate() }
|
||||
|
||||
var status: Int32 = 0
|
||||
|
||||
// We could use Foundation's Thread, but it doesn't work on Linux for some
|
||||
// reason.
|
||||
status = pthread_create(
|
||||
_backgroundThread,
|
||||
nil,
|
||||
{ context in
|
||||
#if canImport(Darwin)
|
||||
let context = context
|
||||
#else
|
||||
let context = context!
|
||||
#endif
|
||||
return context
|
||||
.assumingMemoryBound(to: (() -> UnsafeMutableRawPointer).self)
|
||||
.pointee()
|
||||
},
|
||||
typeErasedBody
|
||||
)
|
||||
|
||||
guard status == 0 else {
|
||||
preconditionFailure("Could not create a background thread")
|
||||
}
|
||||
|
||||
#if canImport(Darwin)
|
||||
guard let backgroundThread = _backgroundThread.pointee else {
|
||||
preconditionFailure("Could not create a background thread")
|
||||
}
|
||||
#else
|
||||
let backgroundThread = _backgroundThread.pointee
|
||||
#endif
|
||||
|
||||
var _resultPtr: UnsafeMutableRawPointer?
|
||||
status = pthread_join(backgroundThread, &_resultPtr)
|
||||
|
||||
guard status == 0, let resultPtr = _resultPtr else {
|
||||
preconditionFailure("Could not join threads")
|
||||
}
|
||||
|
||||
defer { resultPtr.deallocate() }
|
||||
|
||||
return resultPtr.assumingMemoryBound(to: ResultType.self).move()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
// Created by Sergej Jaskiewicz on 02.12.2019.
|
||||
//
|
||||
|
||||
/// A priority queue based on binary min-heap.
|
||||
/// A priproty queue based on binary min-heap.
|
||||
/// If two elements with the same priority are added, the element that was added
|
||||
/// earlier has will have "better" priority (i. e. it will be also extracted earlier).
|
||||
struct FairPriorityQueue<Priority: Comparable, Element> {
|
||||
@@ -26,8 +26,8 @@ struct FairPriorityQueue<Priority: Comparable, Element> {
|
||||
}
|
||||
}
|
||||
|
||||
func min() -> (Priority, Element)? {
|
||||
return storage.first.map { ($0.0.0, $0.1) }
|
||||
func min() -> Element? {
|
||||
return storage.first?.1
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
|
||||
@@ -33,7 +33,7 @@ func testLifecycle<UpstreamOutput, Operator: Publisher>(
|
||||
let emptySubscriber =
|
||||
TrackingSubscriberBase<Operator.Output, Operator.Failure>(onDeinit: onDeinit)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty,
|
||||
"Lifecycle test #1: the subscriber's history should be empty",
|
||||
"Lifecycle test #1: thesubscriber's history should be empty",
|
||||
file: file,
|
||||
line: line)
|
||||
operatorPublisher.subscribe(emptySubscriber)
|
||||
|
||||
@@ -68,7 +68,7 @@ func reduceLikeOperatorMirror(file: StaticString = #file,
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("result", .anything),
|
||||
("initial", .anything),
|
||||
("status", .anything),
|
||||
("status", .contains("awaitingSubscription")),
|
||||
file: file,
|
||||
line: line
|
||||
)
|
||||
@@ -83,7 +83,6 @@ internal func testReflection<Output, Failure: Error, Operator: Publisher>(
|
||||
description expectedDescription: String?,
|
||||
customMirror customMirrorPredicate: ((Mirror) -> Bool)?,
|
||||
playgroundDescription: String?,
|
||||
subscriberIsAlsoSubscription: Bool = true,
|
||||
_ makeOperator: (CustomConnectablePublisherBase<Output, Failure>) -> Operator
|
||||
) throws {
|
||||
let publisher = CustomConnectablePublisherBase<Output, Failure>(subscription: nil)
|
||||
@@ -125,43 +124,6 @@ internal func testReflection<Output, Failure: Error, Operator: Publisher>(
|
||||
file: file,
|
||||
line: line
|
||||
)
|
||||
|
||||
if subscriberIsAlsoSubscription {
|
||||
publisher.send(subscription: CustomSubscription())
|
||||
let subscription = try XCTUnwrap(tracking.subscriptions.first?.underlying)
|
||||
|
||||
XCTAssertEqual((subscription as? CustomStringConvertible)?.description,
|
||||
expectedDescription,
|
||||
file: file,
|
||||
line: line)
|
||||
|
||||
if let customMirrorPredicate = customMirrorPredicate {
|
||||
let customMirror =
|
||||
try XCTUnwrap((subscription as? CustomReflectable)?.customMirror,
|
||||
file: file,
|
||||
line: line)
|
||||
XCTAssert(customMirrorPredicate(customMirror),
|
||||
"customMirror doesn't satisfy the predicate",
|
||||
file: file,
|
||||
line: line)
|
||||
} else {
|
||||
XCTAssertFalse(subscription is CustomReflectable,
|
||||
"subscription shouldn't conform to CustomReflectable")
|
||||
}
|
||||
|
||||
XCTAssertFalse(subscription is CustomDebugStringConvertible,
|
||||
"subscription shouldn't conform to CustomDebugStringConvertible",
|
||||
file: file,
|
||||
line: line)
|
||||
|
||||
XCTAssertEqual(
|
||||
((subscription as? CustomPlaygroundDisplayConvertible)?
|
||||
.playgroundDescription as? String),
|
||||
playgroundDescription,
|
||||
file: file,
|
||||
line: line
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
@@ -200,7 +162,7 @@ internal func testSubscriptionReflection<Sut: Publisher>(
|
||||
}
|
||||
|
||||
XCTAssertFalse(subscription is CustomDebugStringConvertible,
|
||||
"Subscription shouldn't conform to CustomDebugStringConvertible",
|
||||
"subscriber shouldn't conform to CustomDebugStringConvertible",
|
||||
file: file,
|
||||
line: line)
|
||||
|
||||
@@ -212,24 +174,3 @@ internal func testSubscriptionReflection<Sut: Publisher>(
|
||||
line: line
|
||||
)
|
||||
}
|
||||
|
||||
/// Prior to iOS 14 there was a bug in PassthroughSubject and
|
||||
/// CurrentValueSubject when after cancelling the subscription we couldn't
|
||||
/// reflect the subscription.
|
||||
@available(macOS, deprecated: 10.16, message: """
|
||||
If macOS 10.16/11.0 has already been released, this property should be removed
|
||||
""")
|
||||
@available(iOS, deprecated: 14, message: """
|
||||
If iOS 14 has already been released, this property should be removed
|
||||
""")
|
||||
var hasCustomMirrorUseAfterFreeBug: Bool { // swiftlint:disable:this let_var_whitespace
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
if #available(macOS 10.16, iOS 14.0, *) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -11,14 +11,6 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
protocol CancellableTokenProtocol: Cancellable {
|
||||
|
||||
init(_ scheduler: VirtualTimeScheduler)
|
||||
|
||||
var isCancelled: Bool { get }
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class VirtualTimeScheduler: Scheduler {
|
||||
|
||||
@@ -179,42 +171,15 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
final class CancellableToken: CancellableTokenProtocol {
|
||||
|
||||
weak var scheduler: VirtualTimeScheduler?
|
||||
private final class CancellableToken: Cancellable {
|
||||
|
||||
private(set) var isCancelled = false
|
||||
|
||||
init(_ scheduler: VirtualTimeScheduler) {
|
||||
self.scheduler = scheduler
|
||||
}
|
||||
|
||||
deinit {
|
||||
scheduler?.cancellableTokenDeinitCount += 1
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
final class NoopCancellableToken: CancellableTokenProtocol {
|
||||
|
||||
weak var scheduler: VirtualTimeScheduler?
|
||||
|
||||
init(_ scheduler: VirtualTimeScheduler) {
|
||||
self.scheduler = scheduler
|
||||
}
|
||||
|
||||
deinit {
|
||||
scheduler?.cancellableTokenDeinitCount += 1
|
||||
}
|
||||
|
||||
var isCancelled: Bool { return false }
|
||||
|
||||
func cancel() {}
|
||||
}
|
||||
|
||||
enum Event: Equatable, CustomStringConvertible {
|
||||
case now
|
||||
case minimumTolerance
|
||||
@@ -256,7 +221,7 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
"""
|
||||
case let .scheduleAfterDateWithInterval(date, interval, tolerance, options):
|
||||
return """
|
||||
.scheduleAfterDateWithInterval(\(describeDate(date))), \
|
||||
.scheduleAfterDateWithInterval(\(describeDate(date)), \
|
||||
interval: \(describeStride(interval)), \
|
||||
tolerance: \(describeStride(tolerance)), \
|
||||
options: \(describeOptions(options)))
|
||||
@@ -274,14 +239,6 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
|
||||
private var workQueue = FairPriorityQueue<SchedulerTimeType, () -> Void>()
|
||||
|
||||
private let cancellableTokenType: CancellableTokenProtocol.Type
|
||||
|
||||
fileprivate(set) var cancellableTokenDeinitCount = 0
|
||||
|
||||
init(cancellableTokenType: CancellableTokenProtocol.Type = CancellableToken.self) {
|
||||
self.cancellableTokenType = cancellableTokenType
|
||||
}
|
||||
|
||||
var scheduledDates: [SchedulerTimeType] {
|
||||
return workQueue.map { $0.0 }
|
||||
}
|
||||
@@ -293,7 +250,7 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
|
||||
var minimumTolerance: SchedulerTimeType.Stride {
|
||||
history.append(.minimumTolerance)
|
||||
return .nanoseconds(7)
|
||||
return 0
|
||||
}
|
||||
|
||||
func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
|
||||
@@ -318,7 +275,7 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
options: options))
|
||||
let cancellableToken = cancellableTokenType.init(self)
|
||||
let cancellableToken = CancellableToken()
|
||||
repeatedlyExecute(after: date,
|
||||
interval: interval,
|
||||
cancellableToken: cancellableToken,
|
||||
@@ -328,7 +285,7 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
|
||||
private func repeatedlyExecute(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
cancellableToken: CancellableTokenProtocol,
|
||||
cancellableToken: CancellableToken,
|
||||
action: @escaping () -> Void) {
|
||||
let enqueuedAction: () -> Void = { [unowned self] in
|
||||
if cancellableToken.isCancelled { return }
|
||||
@@ -347,20 +304,11 @@ final class VirtualTimeScheduler: Scheduler {
|
||||
/// - Note: The actions that were already executed will not be executed again.
|
||||
/// This function does **not** provide time machine-like functionality.
|
||||
func rewind(to time: SchedulerTimeType) {
|
||||
if time > _now {
|
||||
while let (nextActionTime, action) = workQueue.min(), nextActionTime <= time {
|
||||
workQueue.extractMin()
|
||||
_now = max(nextActionTime, _now)
|
||||
action()
|
||||
}
|
||||
}
|
||||
_now = time
|
||||
}
|
||||
|
||||
func executeScheduledActions(until deadline: SchedulerTimeType = .nanoseconds(.max)) {
|
||||
precondition(deadline >= _now)
|
||||
while let (time, action) = workQueue.min(), time <= deadline {
|
||||
workQueue.extractMin()
|
||||
func executeScheduledActions() {
|
||||
while let (time, action) = workQueue.extractMin() {
|
||||
_now = max(time, _now)
|
||||
action()
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
import XCTest
|
||||
|
||||
// XCTUnwrap is not available when using Swift Package Manager.
|
||||
// This has been fixed in Swift 5.2, but we want to maintain compatibility.
|
||||
#if swift(<5.2)
|
||||
// FIXME: XCTUnwrap is unavailable in Swift Package Manager yet.
|
||||
|
||||
private struct UnwrappingFailure: Error, LocalizedError {
|
||||
|
||||
@@ -54,4 +52,3 @@ public func XCTUnwrap<Result>(_ expression: @autoclosure () throws -> Result?,
|
||||
throw error
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -105,18 +105,18 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSendFailureCompletion() {
|
||||
let passthrough = Sut()
|
||||
let cvs = Sut()
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: { subscription in
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
)
|
||||
|
||||
passthrough.subscribe(subscriber)
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject")])
|
||||
|
||||
passthrough.send(completion: .failure(.oops))
|
||||
cvs.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.completion(.failure(.oops))])
|
||||
@@ -173,22 +173,6 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(subscriber.subscriptions.count, 11)
|
||||
XCTAssertEqual(subscriber.inputs.count, 0)
|
||||
XCTAssertEqual(subscriber.completions.count, 0)
|
||||
|
||||
passthrough.send(0)
|
||||
|
||||
XCTAssertEqual(subscriber.inputs, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
||||
for (i, subscription) in subscriber.subscriptions.enumerated()
|
||||
where i.isMultiple(of: 2)
|
||||
{
|
||||
subscription.cancel()
|
||||
}
|
||||
passthrough.send(1)
|
||||
|
||||
XCTAssertEqual(
|
||||
subscriber.inputs,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
|
||||
)
|
||||
}
|
||||
|
||||
// Reactive Streams Spec: Rule #6
|
||||
@@ -326,97 +310,6 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testCompletion() throws {
|
||||
let passthrough = Sut()
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
|
||||
passthrough.subscribe(tracking)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(12))
|
||||
|
||||
passthrough.send(1)
|
||||
|
||||
expectedChildren(
|
||||
("parent", .contains("PassthroughSubject")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(11)"),
|
||||
("subject", .contains("PassthroughSubject"))
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
|
||||
passthrough.send(completion: .finished)
|
||||
|
||||
if !hasCustomMirrorUseAfterFreeBug {
|
||||
expectedChildren(
|
||||
("parent", "nil"),
|
||||
("downstream", "nil"),
|
||||
("demand", "max(11)"),
|
||||
("subject", "nil")
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),
|
||||
.value(1),
|
||||
.completion(.finished)])
|
||||
|
||||
passthrough.send(completion: .failure(.oops))
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
|
||||
if !hasCustomMirrorUseAfterFreeBug {
|
||||
expectedChildren(
|
||||
("parent", "nil"),
|
||||
("downstream", "nil"),
|
||||
("demand", "max(11)"),
|
||||
("subject", "nil")
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),
|
||||
.value(1),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testCancellation() throws {
|
||||
let passthrough = Sut()
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
|
||||
passthrough.subscribe(tracking)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(12))
|
||||
|
||||
passthrough.send(1)
|
||||
|
||||
expectedChildren(
|
||||
("parent", .contains("PassthroughSubject")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(11)"),
|
||||
("subject", .contains("PassthroughSubject"))
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(3))
|
||||
try XCTUnwrap(downstreamSubscription).request(.max(4))
|
||||
|
||||
if !hasCustomMirrorUseAfterFreeBug {
|
||||
expectedChildren(
|
||||
("parent", "nil"),
|
||||
("downstream", "nil"),
|
||||
("demand", "max(11)"),
|
||||
("subject", "nil")
|
||||
)(Mirror(reflecting: try XCTUnwrap(downstreamSubscription)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),
|
||||
.value(1)])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
@@ -471,76 +364,67 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
|
||||
func testCancelsUpstreamSubscriptionsOnDeinit() {
|
||||
let subscription = CustomSubscription()
|
||||
do {
|
||||
let passthrough = Sut()
|
||||
for _ in 0 ..< 5 {
|
||||
passthrough.send(subscription: subscription)
|
||||
func testSynchronization() {
|
||||
|
||||
let subscriptions = Atomic<[Subscription]>([])
|
||||
let inputs = Atomic<[Int]>([])
|
||||
let completions = Atomic<[Subscribers.Completion<TestingError>]>([])
|
||||
|
||||
let passthrough = Sut()
|
||||
let subscriber = AnySubscriber<Int, TestingError>(
|
||||
receiveSubscription: { subscription in
|
||||
subscriptions.do { $0.append(subscription) }
|
||||
subscription.request(.unlimited)
|
||||
},
|
||||
receiveValue: { value in
|
||||
inputs.do { $0.append(value) }
|
||||
return .none
|
||||
},
|
||||
receiveCompletion: { completion in
|
||||
completions.do { $0.append(completion) }
|
||||
}
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
}
|
||||
|
||||
XCTAssertEqual(subscription.history, [.cancelled,
|
||||
.cancelled,
|
||||
.cancelled,
|
||||
.cancelled,
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testReleasesEverythingOnTermination() {
|
||||
|
||||
enum TerminationReason: CaseIterable {
|
||||
case cancelled
|
||||
case finished
|
||||
case failed
|
||||
}
|
||||
|
||||
for reason in TerminationReason.allCases {
|
||||
weak var weakSubscriber: TrackingSubscriber?
|
||||
weak var weakSubject: Sut?
|
||||
weak var weakSubscription: AnyObject?
|
||||
|
||||
do {
|
||||
let subject = Sut()
|
||||
do {
|
||||
let subscriber = TrackingSubscriber(receiveSubscription: {
|
||||
weakSubscription = $0 as AnyObject
|
||||
})
|
||||
weakSubscriber = subscriber
|
||||
weakSubject = subject
|
||||
|
||||
subject.subscribe(subscriber)
|
||||
}
|
||||
|
||||
switch reason {
|
||||
case .cancelled:
|
||||
(weakSubscription as? Subscription)?.cancel()
|
||||
case .finished:
|
||||
subject.send(completion: .finished)
|
||||
case .failed:
|
||||
subject.send(completion: .failure(.oops))
|
||||
}
|
||||
|
||||
XCTAssertNil(weakSubscriber, "Subscriber leaked - \(reason)")
|
||||
XCTAssertNil(weakSubscription, "Subscription leaked - \(reason)")
|
||||
}
|
||||
|
||||
XCTAssertNil(weakSubject, "Subject leaked - \(reason)")
|
||||
}
|
||||
}
|
||||
|
||||
func testConduitReflection() throws {
|
||||
try testSubscriptionReflection(
|
||||
description: "PassthroughSubject",
|
||||
customMirror: expectedChildren(
|
||||
("parent", .contains("PassthroughSubject")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(0)"),
|
||||
("subject", .contains("PassthroughSubject"))
|
||||
),
|
||||
playgroundDescription: "PassthroughSubject",
|
||||
sut: PassthroughSubject<Int, Error>()
|
||||
)
|
||||
|
||||
race(
|
||||
{
|
||||
passthrough.subscribe(subscriber)
|
||||
},
|
||||
{
|
||||
passthrough.subscribe(subscriber)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(subscriptions.value.count, 200)
|
||||
|
||||
race(
|
||||
{
|
||||
passthrough.send(31)
|
||||
},
|
||||
{
|
||||
passthrough.send(42)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(inputs.value.count, 40000)
|
||||
|
||||
race(
|
||||
{
|
||||
subscriptions.value[0].request(.max(4))
|
||||
},
|
||||
{
|
||||
subscriptions.value[0].request(.max(10))
|
||||
}
|
||||
)
|
||||
|
||||
race(
|
||||
{
|
||||
passthrough.send(completion: .finished)
|
||||
},
|
||||
{
|
||||
passthrough.send(completion: .failure(""))
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(completions.value.count, 200)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ final class AssertNoFailureTests: XCTestCase {
|
||||
("prefix", "PREFIX")
|
||||
),
|
||||
playgroundDescription: "AssertNoFailure",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.assertNoFailure("PREFIX", file: "SomeFile.swift", line: 1987) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,7 +151,6 @@ final class AutoconnectTests: XCTestCase {
|
||||
description: "Autoconnect",
|
||||
customMirror: customMirrorPredicate,
|
||||
playgroundDescription: "Autoconnect",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.autoconnect() })
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
|
||||
@@ -168,7 +168,6 @@ final class BreakpointTests: XCTestCase {
|
||||
("upstream", .contains("CustomConnectablePublisherBase"))
|
||||
),
|
||||
playgroundDescription: "Breakpoint",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.breakpointOnError() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,953 +0,0 @@
|
||||
//
|
||||
// BufferTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.01.2020.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class BufferTests: XCTestCase {
|
||||
|
||||
func testInitialDemandWithKeepFullPrefetchStrategy() {
|
||||
testInitialDemand(
|
||||
withPrefetchStrategy: .keepFull,
|
||||
expectedSubscriptionHistory: [.requested(.max(42))]
|
||||
)
|
||||
}
|
||||
|
||||
func testInitialDemandWithByRequestPrefetchStrategy() {
|
||||
testInitialDemand(
|
||||
withPrefetchStrategy: .byRequest,
|
||||
expectedSubscriptionHistory: [.requested(.unlimited)]
|
||||
)
|
||||
}
|
||||
|
||||
func testBufferingInputDroppingNewest() throws {
|
||||
try testBufferingInput(whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferingInputDroppingOldest() throws {
|
||||
try testBufferingInput(whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferingInputFailingWhenBufferIsFull() throws {
|
||||
try testBufferingInput(whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testReceiveValueAfterFinishing() throws {
|
||||
try testReceiveValueAfterCompleting(.finished)
|
||||
}
|
||||
|
||||
func testReceiveValueAfterFailing() throws {
|
||||
try testReceiveValueAfterCompleting(.failure(.oops))
|
||||
}
|
||||
|
||||
func testDeadlockWhenErroringOnFullBuffer() {
|
||||
var recursionCounter = 10
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { publisher in
|
||||
publisher.buffer(
|
||||
size: 0,
|
||||
prefetch: .keepFull,
|
||||
whenFull: .customError {
|
||||
if recursionCounter == 0 { return TestingError.oops }
|
||||
recursionCounter -= 1
|
||||
_ = publisher.send(1000)
|
||||
return TestingError.oops
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(0)
|
||||
}
|
||||
}
|
||||
|
||||
func testRecursionByRequestDropNewest() {
|
||||
testRecursion(prefetch: .byRequest, whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testRecursionByRequestDropOldest() {
|
||||
testRecursion(prefetch: .byRequest, whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testRecursionByRequestCustomError() {
|
||||
testRecursion(prefetch: .byRequest, whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testRecursionKeepFullDropNewest() {
|
||||
testRecursion(prefetch: .keepFull, whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testRecursionKeepFullDropOldest() {
|
||||
testRecursion(prefetch: .keepFull, whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testRecursionKeepFullCustomError() {
|
||||
testRecursion(prefetch: .keepFull, whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testRequestingUnlimitedDemandByRequestDropNewest() {
|
||||
testRequestingUnlimitedDemand(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testRequestingUnlimitedDemandByRequestDropOldest() {
|
||||
testRequestingUnlimitedDemand(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testRequestingUnlimitedDemandByRequestCustomError() {
|
||||
testRequestingUnlimitedDemand(prefetch: .byRequest,
|
||||
whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testRequestingUnlimitedDemandKeepFullDropNewest() {
|
||||
testRequestingUnlimitedDemand(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testRequestingUnlimitedDemandKeepFullDropOldest() {
|
||||
testRequestingUnlimitedDemand(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testRequestingUnlimitedDemandKeepFullCustomError() {
|
||||
testRequestingUnlimitedDemand(prefetch: .keepFull,
|
||||
whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testRequestingFiniteDemandByRequestDropNewest() {
|
||||
testRequestingFiniteDemand(prefetch: .byRequest, whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testRequestingFiniteDemandByRequestDropOldest() {
|
||||
testRequestingFiniteDemand(prefetch: .byRequest, whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testRequestingFiniteDemandByRequestCustomError() {
|
||||
testRequestingFiniteDemand(prefetch: .byRequest, whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testRequestingFiniteDemandKeepFullDropNewest() {
|
||||
testRequestingFiniteDemand(prefetch: .keepFull, whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testRequestingFiniteDemandKeepFullDropOldest() {
|
||||
testRequestingFiniteDemand(prefetch: .keepFull, whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testRequestingFiniteDemandKeepFullCustomError() {
|
||||
testRequestingFiniteDemand(prefetch: .keepFull, whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testBufferByRequestDropNewestReceiveSubscriptionTwice() throws {
|
||||
try testBufferReceiveSubscriptionTwice(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferByRequestDropOldestReceiveSubscriptionTwice() throws {
|
||||
try testBufferReceiveSubscriptionTwice(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferByRequestCustomErrorReceiveSubscriptionTwice() throws {
|
||||
try testBufferReceiveSubscriptionTwice(prefetch: .byRequest,
|
||||
whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropNewestReceiveSubscriptionTwice() throws {
|
||||
try testBufferReceiveSubscriptionTwice(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropOldestReceiveSubscriptionTwice() throws {
|
||||
try testBufferReceiveSubscriptionTwice(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullCustomErrorReceiveSubscriptionTwice() throws {
|
||||
try testBufferReceiveSubscriptionTwice(prefetch: .keepFull,
|
||||
whenFull: .customError { .oops })
|
||||
}
|
||||
|
||||
func testBufferByRequestDropNewestReceiveValueBeforeSubscription() {
|
||||
testBufferReceiveValueBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferByRequestDropOldestReceiveValueBeforeSubscription() {
|
||||
testBufferReceiveValueBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferByRequestCustomErrorReceiveValueBeforeSubscription() {
|
||||
testBufferReceiveValueBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropNewestReceiveValueBeforeSubscription() {
|
||||
testBufferReceiveValueBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropOldestReceiveValueBeforeSubscription() {
|
||||
testBufferReceiveValueBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullCustomErrorReceiveValueBeforeSubscription() {
|
||||
testBufferReceiveValueBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferByRequestDropNewestReceiveCompletionBeforeSubscription() {
|
||||
testBufferReceiveCompletionBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferByRequestDropOldestReceiveCompletionBeforeSubscription() {
|
||||
testBufferReceiveCompletionBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferByRequestCustomErrorReceiveCompletionBeforeSubscription() {
|
||||
testBufferReceiveCompletionBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropNewestReceiveCompletionBeforeSubscription() {
|
||||
testBufferReceiveCompletionBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropOldestReceiveCompletionBeforeSubscription() {
|
||||
testBufferReceiveCompletionBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullCustomErrorReceiveCompletionBeforeSubscription() {
|
||||
testBufferReceiveCompletionBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferByRequestDropNewestRequestBeforeSubscription() {
|
||||
testBufferRequestBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferByRequestDropOldestRequestBeforeSubscription() {
|
||||
testBufferRequestBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferByRequestCustomErrorRequestBeforeSubscription() {
|
||||
testBufferRequestBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropNewestRequestBeforeSubscription() {
|
||||
testBufferRequestBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropOldestRequestBeforeSubscription() {
|
||||
testBufferRequestBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullCustomErrorRequestBeforeSubscription() {
|
||||
testBufferRequestBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferByRequestDropNewestCancelBeforeSubscription() {
|
||||
testBufferCancelBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferByRequestDropOldestCancelBeforeSubscription() {
|
||||
testBufferCancelBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferByRequestCustomErrorCancelBeforeSubscription() {
|
||||
testBufferCancelBeforeSubscription(prefetch: .byRequest,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropNewestCancelBeforeSubscription() {
|
||||
testBufferCancelBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropOldestCancelBeforeSubscription() {
|
||||
testBufferCancelBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullCustomErrorCancelBeforeSubscription() {
|
||||
testBufferCancelBeforeSubscription(prefetch: .keepFull,
|
||||
whenFull: .customError(unreachable))
|
||||
}
|
||||
|
||||
func testFailWhileSendingValues() throws {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.buffer(size: 5, prefetch: .byRequest, whenFull: .dropOldest) }
|
||||
)
|
||||
|
||||
helper.tracking.onValue = { _ in
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
XCTAssertEqual(helper.publisher.send(4), .none)
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(3))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.completion(.failure(.oops)),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
}
|
||||
|
||||
func testBufferByRequestDropNewestLifecycle() {
|
||||
testBufferLifecycle(prefetch: .byRequest,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferByRequestDropOldestLifecycle() {
|
||||
testBufferLifecycle(prefetch: .byRequest,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferByRequestCustomErrorLifecycle() {
|
||||
testBufferLifecycle(prefetch: .byRequest,
|
||||
whenFull: .customError { TestingError.oops })
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropNewestLifecycle() {
|
||||
testBufferLifecycle(prefetch: .keepFull,
|
||||
whenFull: .dropNewest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullDropOldestLifecycle() {
|
||||
testBufferLifecycle(prefetch: .keepFull,
|
||||
whenFull: .dropOldest)
|
||||
}
|
||||
|
||||
func testBufferKeepFullCustomErrorLifecycle() {
|
||||
testBufferLifecycle(prefetch: .keepFull,
|
||||
whenFull: .customError { TestingError.oops })
|
||||
}
|
||||
|
||||
func testBufferReflection() throws {
|
||||
try testReflection(
|
||||
parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "Buffer",
|
||||
customMirror: expectedChildren(
|
||||
("values", "[]"),
|
||||
("state", .anything),
|
||||
("downstreamDemand", "max(0)"),
|
||||
("terminal", "nil")
|
||||
),
|
||||
playgroundDescription: "Buffer",
|
||||
{ $0.buffer(size: 13, prefetch: .keepFull, whenFull: .dropNewest) }
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Generic tests
|
||||
|
||||
private func testInitialDemand(
|
||||
withPrefetchStrategy prefetch: Publishers.PrefetchStrategy,
|
||||
expectedSubscriptionHistory: [CustomSubscription.Event]
|
||||
) {
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: nil)
|
||||
let tracking = TrackingSubscriber()
|
||||
|
||||
subscription.onRequest = { _ in
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
}
|
||||
|
||||
let buffer = publisher
|
||||
.buffer(size: 42, prefetch: prefetch, whenFull: .dropOldest)
|
||||
|
||||
buffer.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
|
||||
publisher.send(subscription: subscription)
|
||||
|
||||
XCTAssertEqual(subscription.history, expectedSubscriptionHistory)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Buffer")])
|
||||
}
|
||||
|
||||
private func testBufferingInput(
|
||||
whenFull: Publishers.BufferingStrategy<TestingError>
|
||||
) throws {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.buffer(size: 3, prefetch: .byRequest, whenFull: whenFull) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
XCTAssertEqual(helper.publisher.send(4), .none)
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer")])
|
||||
switch whenFull {
|
||||
case .dropNewest, .dropOldest:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
case .customError:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(3))
|
||||
|
||||
switch whenFull {
|
||||
case .dropNewest:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(3))])
|
||||
case .dropOldest:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(5)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(3))])
|
||||
case .customError:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled,
|
||||
.requested(.max(3))])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
|
||||
|
||||
switch whenFull {
|
||||
case .dropNewest:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(3)),
|
||||
.requested(.max(1))])
|
||||
case .dropOldest:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(5)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(3)),
|
||||
.requested(.max(1))])
|
||||
case .customError:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled,
|
||||
.requested(.max(3))])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func testRecursion(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<TestingError>
|
||||
) {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.buffer(size: 5, prefetch: prefetch, whenFull: whenFull)
|
||||
}
|
||||
)
|
||||
|
||||
helper.tracking.onValue = { _ in
|
||||
helper.downstreamSubscription?.request(.max(1))
|
||||
helper.downstreamSubscription?.request(.none)
|
||||
}
|
||||
|
||||
helper.downstreamSubscription?.request(.max(3))
|
||||
|
||||
for i in 0 ..< 10 {
|
||||
switch prefetch {
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.publisher.send(i), .none)
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.publisher.send(i), .max(1))
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(0),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7),
|
||||
.value(8),
|
||||
.value(9)])
|
||||
|
||||
switch prefetch {
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(3))])
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(5)),
|
||||
.requested(.max(3))])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func testRequestingFiniteDemand(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<TestingError>
|
||||
) {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: Subscribers.Demand.none,
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.buffer(size: 5, prefetch: prefetch, whenFull: whenFull)
|
||||
}
|
||||
)
|
||||
|
||||
for i in 0 ..< 10 {
|
||||
XCTAssertEqual(helper.publisher.send(i), .none)
|
||||
}
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer")])
|
||||
|
||||
switch (prefetch, whenFull) {
|
||||
case (.byRequest, .customError):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.none),
|
||||
.cancelled])
|
||||
case (.byRequest, _):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.none)])
|
||||
case (.keepFull, .customError):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(5)),
|
||||
.requested(.none),
|
||||
.cancelled])
|
||||
case (.keepFull, _):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(5)),
|
||||
.requested(.none)])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
|
||||
helper.downstreamSubscription?.request(.max(3))
|
||||
helper.downstreamSubscription?.request(.max(1))
|
||||
|
||||
switch whenFull {
|
||||
case .dropNewest:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(0),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
case .dropOldest:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7),
|
||||
.value(8)])
|
||||
case .customError:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(0),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.failure(.oops))])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (prefetch, whenFull) {
|
||||
case (.byRequest, .customError):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.none),
|
||||
.cancelled,
|
||||
.requested(.max(3))])
|
||||
case (.byRequest, _):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.none),
|
||||
.requested(.max(3)),
|
||||
.requested(.max(1))])
|
||||
case (.keepFull, .customError):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(5)),
|
||||
.requested(.none),
|
||||
.cancelled,
|
||||
.requested(.max(6))])
|
||||
case (.keepFull, _):
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(5)),
|
||||
.requested(.none),
|
||||
.requested(.max(6)),
|
||||
.requested(.max(2))])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func testRequestingUnlimitedDemand(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<TestingError>
|
||||
) {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.buffer(size: 5, prefetch: prefetch, whenFull: whenFull)
|
||||
}
|
||||
)
|
||||
|
||||
for i in 0 ..< 10 {
|
||||
switch prefetch {
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.publisher.send(i), .none)
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.publisher.send(i), .max(1))
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(0),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7),
|
||||
.value(8),
|
||||
.value(9)])
|
||||
|
||||
switch prefetch {
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(5)),
|
||||
.requested(.unlimited)])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func testReceiveValueAfterCompleting(
|
||||
_ completion: Subscribers.Completion<TestingError>
|
||||
) throws {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.buffer(size: 3, prefetch: .byRequest, whenFull: .dropOldest) }
|
||||
)
|
||||
|
||||
helper.publisher.send(completion: completion)
|
||||
helper.publisher.send(completion: .finished) // Should be ignored
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
switch completion {
|
||||
case .finished:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer")])
|
||||
case .failure:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
|
||||
switch completion {
|
||||
case .finished:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer")])
|
||||
case .failure:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(2))
|
||||
|
||||
switch completion {
|
||||
case .finished:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.value(2)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(2))])
|
||||
case .failure:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.completion(completion)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
|
||||
|
||||
switch completion {
|
||||
case .finished:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(completion)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.requested(.max(2)),
|
||||
.requested(.max(1))])
|
||||
case .failure:
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Buffer"),
|
||||
.completion(completion)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
}
|
||||
|
||||
private func testBufferLifecycle(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<TestingError>
|
||||
) {
|
||||
var deinitCounter = 0
|
||||
let onDeinit = { deinitCounter += 1 }
|
||||
|
||||
do {
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
publisher.onDeinit = onDeinit
|
||||
let buffer = publisher.buffer(size: 1, prefetch: prefetch, whenFull: whenFull)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.unlimited) },
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
buffer.subscribe(tracking)
|
||||
publisher.send(completion: .finished)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
|
||||
do {
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
publisher.onDeinit = onDeinit
|
||||
let buffer = publisher.buffer(size: 1, prefetch: prefetch, whenFull: whenFull)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.unlimited) },
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
buffer.subscribe(tracking)
|
||||
publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 4)
|
||||
|
||||
var downstreamSubscription: Subscription?
|
||||
|
||||
do {
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
publisher.onDeinit = onDeinit
|
||||
let buffer = publisher.buffer(size: 1, prefetch: prefetch, whenFull: whenFull)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 },
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
buffer.subscribe(tracking)
|
||||
publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 6)
|
||||
downstreamSubscription?.cancel()
|
||||
XCTAssertEqual(deinitCounter, 6)
|
||||
|
||||
do {
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
publisher.onDeinit = onDeinit
|
||||
let buffer = publisher.buffer(size: 1, prefetch: prefetch, whenFull: whenFull)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.unlimited) },
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
buffer.subscribe(tracking)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 6)
|
||||
}
|
||||
|
||||
private func testBufferReceiveSubscriptionTwice(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<TestingError>
|
||||
) throws {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.buffer(size: 2, prefetch: prefetch, whenFull: whenFull) }
|
||||
)
|
||||
|
||||
switch prefetch {
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2))])
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
|
||||
let secondSubscription = CustomSubscription()
|
||||
|
||||
try XCTUnwrap(helper.publisher.subscriber)
|
||||
.receive(subscription: secondSubscription)
|
||||
|
||||
XCTAssertEqual(secondSubscription.history, [.cancelled])
|
||||
|
||||
try XCTUnwrap(helper.publisher.subscriber)
|
||||
.receive(subscription: helper.subscription)
|
||||
|
||||
switch prefetch {
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2)),
|
||||
.cancelled])
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
switch prefetch {
|
||||
case .keepFull:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2)),
|
||||
.cancelled,
|
||||
.cancelled])
|
||||
case .byRequest:
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
|
||||
.cancelled,
|
||||
.cancelled])
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
@unknown default:
|
||||
unreachable()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func testBufferReceiveValueBeforeSubscription(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Never>
|
||||
) {
|
||||
testReceiveValueBeforeSubscription(
|
||||
value: 0,
|
||||
expected: .history([], demand: .none),
|
||||
{ $0.buffer(size: 2, prefetch: prefetch, whenFull: whenFull) }
|
||||
)
|
||||
}
|
||||
|
||||
private func testBufferReceiveCompletionBeforeSubscription(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Never>
|
||||
) {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([]),
|
||||
{ $0.buffer(size: 2, prefetch: prefetch, whenFull: whenFull) }
|
||||
)
|
||||
}
|
||||
|
||||
private func testBufferRequestBeforeSubscription(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Never>
|
||||
) {
|
||||
testRequestBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.buffer(size: 2, prefetch: prefetch, whenFull: whenFull) }
|
||||
)
|
||||
}
|
||||
|
||||
private func testBufferCancelBeforeSubscription(
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Never>
|
||||
) {
|
||||
testCancelBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.buffer(size: 2, prefetch: prefetch, whenFull: whenFull) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,622 +0,0 @@
|
||||
//
|
||||
// CatchTests.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 CatchTests: XCTestCase {
|
||||
|
||||
// MARK: - Catch
|
||||
|
||||
func testSimpleCatch() {
|
||||
CatchTests
|
||||
.testWithSequence(expectedSubscription: "Catch") { upstream, new in
|
||||
upstream.catch { _ in new }
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchReceiveSubscriptionTwice() throws {
|
||||
try testReceiveSubscriptionTwice { $0.catch(Fail.init) }
|
||||
try testReceiveSubscriptionTwice { publisher in
|
||||
Fail(outputType: Int.self, failure: TestingError.oops)
|
||||
.catch { _ in publisher }
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchCrashesOnUnwantedInput() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.catch { _ in Just(-1) } })
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(42)
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchPreservesDemand() throws {
|
||||
try CatchTests.testPreservesDemand(expectedSubscription: "Catch") {
|
||||
$0.catch($1)
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchUpstreamFinishes() {
|
||||
CatchTests.testUpstreamFinishes(expectedSubscription: "Catch") {
|
||||
$0.catch($1)
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchRecursion() {
|
||||
testRecursion(expectedSubscription: "Catch") {
|
||||
$0.catch($1)
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(value: 1, expected: .crash) {
|
||||
$0.catch { _ in Just(13) }
|
||||
}
|
||||
}
|
||||
|
||||
func testCatchReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([]),
|
||||
{ $0.catch { _ in Just(13) } }
|
||||
)
|
||||
}
|
||||
|
||||
func testCatchRequestPendingPost() {
|
||||
CatchTests.testRequestPendingPost(expectedSubscription: "Catch",
|
||||
{ $0.catch($1) })
|
||||
}
|
||||
|
||||
func testCatchCancelPendingPost() {
|
||||
CatchTests.testCancelPendingPost(expectedSubscription: "Catch",
|
||||
{ $0.catch($1) })
|
||||
}
|
||||
|
||||
func testCatchRequestPost() throws {
|
||||
try CatchTests.testRequestPost(expectedSubscription: "Catch",
|
||||
{ $0.catch($1) })
|
||||
}
|
||||
|
||||
func testCatchCancellationBeforeRecovering() throws {
|
||||
try CatchTests.testCancellationBeforeRecovering(expectedSubscription: "Catch",
|
||||
{ $0.catch($1) })
|
||||
}
|
||||
|
||||
func testCatchCancellationAfterRecovering() throws {
|
||||
try CatchTests.testCancellationAfterRecovering(expectedSubscription: "Catch",
|
||||
{ $0.catch($1) })
|
||||
}
|
||||
|
||||
func testCatchUpstreamFailsTwice() {
|
||||
testUpstreamFailsTwice(expectedSubscription: "Catch") { $0.catch($1) }
|
||||
}
|
||||
|
||||
func testCatchReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "Catch",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
playgroundDescription: "Catch",
|
||||
{ $0.catch(Fail.init) })
|
||||
|
||||
try testReflection(
|
||||
parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "Catch",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
playgroundDescription: "Catch",
|
||||
{ publisher in
|
||||
Fail(outputType: Int.self, failure: TestingError.oops)
|
||||
.catch { _ in publisher }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - TryCatch
|
||||
|
||||
func testSimpleTryCatch() {
|
||||
CatchTests
|
||||
.testWithSequence(expectedSubscription: "TryCatch") { upstream, new in
|
||||
upstream.tryCatch { _ in new }
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchReceiveSubscriptionTwice() throws {
|
||||
|
||||
try testReceiveSubscriptionTwice { $0.tryCatch(Fail.init) }
|
||||
|
||||
try testReceiveSubscriptionTwice { publisher in
|
||||
Fail(outputType: Int.self, failure: TestingError.oops)
|
||||
.tryCatch { _ in publisher }
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchCrashesOnUnwantedInput() {
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.tryCatch { _ in Just(-1) } })
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(42)
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchPreservesDemand() throws {
|
||||
try CatchTests.testPreservesDemand(expectedSubscription: "TryCatch") {
|
||||
$0.tryCatch($1)
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchUpstreamFinishes() {
|
||||
CatchTests.testUpstreamFinishes(expectedSubscription: "TryCatch") {
|
||||
$0.tryCatch($1)
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchHandlerThrows() {
|
||||
|
||||
var handledErrors = [TestingError]()
|
||||
|
||||
func handler(_ error: TestingError) throws -> Just<Int> {
|
||||
handledErrors.append(error)
|
||||
throw "oops2" as TestingError
|
||||
}
|
||||
|
||||
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.tryCatch(handler) })
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history,
|
||||
[.subscription("TryCatch"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.failure("oops2" as TestingError))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(handledErrors, [.oops])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(-1), .none)
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history,
|
||||
[.subscription("TryCatch"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.failure("oops2" as TestingError)),
|
||||
.value(-1)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(handledErrors, [.oops])
|
||||
}
|
||||
|
||||
func testTryCatchRecursion() {
|
||||
testRecursion(expectedSubscription: "TryCatch") {
|
||||
$0.tryCatch($1)
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(value: 1, expected: .crash) {
|
||||
$0.tryCatch { _ in Just(13) }
|
||||
}
|
||||
}
|
||||
|
||||
func testTryCatchReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([]),
|
||||
{ $0.tryCatch { _ in Just(13) } }
|
||||
)
|
||||
}
|
||||
|
||||
func testTryCatchRequestPendingPost() {
|
||||
CatchTests.testRequestPendingPost(expectedSubscription: "TryCatch",
|
||||
{ $0.tryCatch($1) })
|
||||
}
|
||||
|
||||
func testTryCatchCancelPendingPost() {
|
||||
CatchTests.testCancelPendingPost(expectedSubscription: "TryCatch",
|
||||
{ $0.tryCatch($1) })
|
||||
}
|
||||
|
||||
func testTryCatchRequestPost() throws {
|
||||
try CatchTests.testRequestPost(expectedSubscription: "TryCatch",
|
||||
{ $0.tryCatch($1) })
|
||||
}
|
||||
|
||||
func testTryCatchCancellationBeforeRecovering() throws {
|
||||
try CatchTests.testCancellationBeforeRecovering(expectedSubscription: "TryCatch",
|
||||
{ $0.tryCatch($1) })
|
||||
}
|
||||
|
||||
func testTryCatchCancellationAfterRecovering() throws {
|
||||
try CatchTests.testCancellationAfterRecovering(expectedSubscription: "TryCatch",
|
||||
{ $0.tryCatch($1) })
|
||||
}
|
||||
|
||||
func testTryCatchUpstreamFailsTwice() {
|
||||
testUpstreamFailsTwice(expectedSubscription: "TryCatch") { $0.tryCatch($1) }
|
||||
}
|
||||
|
||||
func testTryCatchReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "TryCatch",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
playgroundDescription: "TryCatch",
|
||||
{ $0.tryCatch(Fail.init) })
|
||||
|
||||
try testReflection(
|
||||
parentInput: Int.self,
|
||||
parentFailure: TestingError.self,
|
||||
description: "TryCatch",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
playgroundDescription: "TryCatch",
|
||||
{ publisher in
|
||||
Fail<Int, TestingError>(error: .oops).tryCatch { _ in publisher }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Generic tests
|
||||
|
||||
private typealias TestSequence = Publishers.Sequence<[Int], Never>
|
||||
|
||||
private static func testWithSequence<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (Publishers.TryMap<TestSequence, Int>, TestSequence) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
let throwingSequence = TestSequence(sequence: Array(0 ..< 10))
|
||||
.tryMap { v -> Int in
|
||||
if v < 5 {
|
||||
return v
|
||||
} else {
|
||||
throw TestingError.oops
|
||||
}
|
||||
}
|
||||
|
||||
let `catch` = makeCatch(throwingSequence, [3, 2, 1, 0].publisher)
|
||||
|
||||
let tracking = TrackingSubscriberBase<Int, Operator.Failure>(
|
||||
receiveSubscription: { $0.request(.max(1)) },
|
||||
receiveValue: { _ in .max(1) }
|
||||
)
|
||||
`catch`.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(expectedSubscription),
|
||||
.value(0),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(3),
|
||||
.value(2),
|
||||
.value(1),
|
||||
.value(0),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
private static func testPreservesDemand<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch:
|
||||
(CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisherBase<Int, Error>) -> Operator
|
||||
) throws where Operator.Output == Int {
|
||||
|
||||
let errorHandlerSubscription = CustomSubscription()
|
||||
let errorHandlerPublisher = CustomPublisherBase<Int, Error>(
|
||||
subscription: errorHandlerSubscription
|
||||
)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(12),
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { makeCatch($0) { _ in errorHandlerPublisher } }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(12))])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(5))
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(12)),
|
||||
.requested(.max(5))])
|
||||
|
||||
for i in 1 ... 8 {
|
||||
XCTAssertEqual(helper.publisher.send(i), .max(3))
|
||||
}
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7),
|
||||
.value(8)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(12)),
|
||||
.requested(.max(5))])
|
||||
XCTAssertEqual(errorHandlerSubscription.history, [.requested(.max(33))])
|
||||
}
|
||||
|
||||
private static func testUpstreamFinishes<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> Fail<Int, Error>) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
var counter = 0
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { makeCatch($0) { counter += 1; return Fail(error: $0) } }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
helper.publisher.send(completion: .finished)
|
||||
helper.publisher.send(completion: .finished)
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(2))])
|
||||
XCTAssertEqual(counter, 0)
|
||||
}
|
||||
|
||||
private func testRecursion<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: @escaping (CustomPublisher,
|
||||
@escaping (TestingError) -> Just<Int>) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
|
||||
func createSut(_ publisher: CustomPublisher) -> Operator {
|
||||
return makeCatch(publisher) { _ in
|
||||
publisher.send(completion: .failure(.oops))
|
||||
return Just(13)
|
||||
}
|
||||
}
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: createSut
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
|
||||
assertCrashes {
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
}
|
||||
|
||||
private static func testRequestPendingPost<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisher) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
|
||||
let handlerSubscription = CustomSubscription()
|
||||
let handlerPublisher = CustomPublisher(subscription: handlerSubscription)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(3),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { makeCatch($0) { _ in handlerPublisher } }
|
||||
)
|
||||
|
||||
handlerPublisher.willSubscribe = { _ in
|
||||
guard let downstreamSubscription = helper.downstreamSubscription else {
|
||||
XCTFail("missing downstream subscription")
|
||||
return
|
||||
}
|
||||
downstreamSubscription.request(.max(10))
|
||||
XCTAssertEqual(handlerSubscription.history, [])
|
||||
}
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(1)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(3))])
|
||||
XCTAssertEqual(handlerSubscription.history, [.requested(.max(12))])
|
||||
}
|
||||
|
||||
private static func testCancelPendingPost<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisher) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
|
||||
let handlerSubscription = CustomSubscription()
|
||||
let handlerPublisher = CustomPublisher(subscription: handlerSubscription)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(3),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { makeCatch($0) { _ in handlerPublisher } }
|
||||
)
|
||||
|
||||
handlerPublisher.willSubscribe = { _ in
|
||||
guard let downstreamSubscription = helper.downstreamSubscription else {
|
||||
XCTFail("missing downstream subscription")
|
||||
return
|
||||
}
|
||||
downstreamSubscription.cancel()
|
||||
XCTAssertEqual(handlerSubscription.history, [])
|
||||
}
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(3))])
|
||||
XCTAssertEqual(handlerSubscription.history, [.requested(.max(3))])
|
||||
|
||||
handlerPublisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(handlerSubscription.history, [.requested(.max(3))])
|
||||
}
|
||||
|
||||
private static func testRequestPost<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisher) -> Operator
|
||||
) throws where Operator.Output == Int {
|
||||
|
||||
let handlerSubscription = CustomSubscription()
|
||||
let handlerPublisher = CustomPublisher(subscription: handlerSubscription)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { makeCatch($0) { _ in handlerPublisher } }
|
||||
)
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertNotNil(handlerPublisher.subscriber)
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(12))
|
||||
XCTAssertEqual(handlerSubscription.history, [.requested(.max(12))])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
||||
|
||||
XCTAssertEqual(handlerPublisher.send(100), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(100)])
|
||||
}
|
||||
|
||||
private static func testCancellationBeforeRecovering<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisher) -> Operator
|
||||
) throws where Operator.Output == Int {
|
||||
|
||||
func handler(_ error: TestingError) -> CustomPublisher {
|
||||
XCTFail("Should not be called")
|
||||
return CustomPublisher(subscription: CustomSubscription())
|
||||
}
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { makeCatch($0, handler) }
|
||||
)
|
||||
|
||||
let downstreamSubscription = try XCTUnwrap(helper.downstreamSubscription)
|
||||
|
||||
downstreamSubscription.cancel()
|
||||
downstreamSubscription.request(.max(199))
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .max(3))
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(1)), .cancelled])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(1)])
|
||||
}
|
||||
|
||||
private static func testCancellationAfterRecovering<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisher) -> Operator
|
||||
) throws where Operator.Output == Int {
|
||||
|
||||
let handlerSubscription = CustomSubscription()
|
||||
let handlerPublisher = CustomPublisher(subscription: handlerSubscription)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { makeCatch($0) { _ in handlerPublisher } }
|
||||
)
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
let downstreamSubscription = try XCTUnwrap(helper.downstreamSubscription)
|
||||
downstreamSubscription.cancel()
|
||||
|
||||
XCTAssertEqual(handlerPublisher.send(1), .max(3))
|
||||
handlerPublisher.send(completion: .finished)
|
||||
handlerPublisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(1)])
|
||||
XCTAssertEqual(helper.subscription.history, [])
|
||||
XCTAssertEqual(handlerSubscription.history, [.cancelled])
|
||||
|
||||
let extraSubscription = CustomSubscription()
|
||||
handlerPublisher.send(subscription: extraSubscription)
|
||||
XCTAssertEqual(extraSubscription.history, [.cancelled])
|
||||
}
|
||||
|
||||
private func testUpstreamFailsTwice<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
_ makeCatch: (CustomPublisher,
|
||||
@escaping (TestingError) -> CustomPublisher) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
|
||||
let handlerSubscription = CustomSubscription()
|
||||
let handlerPublisher = CustomPublisher(subscription: handlerSubscription)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .max(3),
|
||||
createSut: { makeCatch($0) { _ in handlerPublisher } }
|
||||
)
|
||||
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
assertCrashes {
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ final class CollectByCountTests: XCTestCase {
|
||||
description: "CollectByCount",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("upstreamSubscription", .anything),
|
||||
("upstreamSubscription", "nil"),
|
||||
("buffer", "[]"),
|
||||
("count", "53")
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@ final class CollectTests: XCTestCase {
|
||||
|
||||
func testtestUpstreamFinishesImmediately() {
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "Collect",
|
||||
expectedResult: [Int](),
|
||||
expectedResult: [],
|
||||
{ $0.collect() })
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ final class ConcatenateTests: XCTestCase {
|
||||
var didSubscribe = false
|
||||
|
||||
publisher.didSubscribe = { _ in
|
||||
XCTAssertEqual(tracking.history, [.subscription("Concatenate")])
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
didSubscribe = true
|
||||
}
|
||||
|
||||
@@ -430,7 +430,7 @@ final class ConcatenateTests: XCTestCase {
|
||||
description: "Concatenate",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("upstreamSubscription", .anything),
|
||||
("upstreamSubscription", "nil"),
|
||||
("suffix", .contains("(sequence: [2.0, 3.0, 5.0, 7.0])")),
|
||||
("demand", "max(0)")
|
||||
),
|
||||
|
||||
@@ -1,630 +0,0 @@
|
||||
//
|
||||
// DebounceTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.06.2020.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class DebounceTests: XCTestCase {
|
||||
|
||||
func testBasicBehavior() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .max(1),
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: scheduler,
|
||||
options: .nontrivialOptions)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(4))
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
scheduler.rewind(to: .nanoseconds(9))
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(200))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.value(3)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(17),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(22),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
helper.publisher.send(completion: .finished)
|
||||
helper.publisher.send(completion: .failure(.oops)) // ignored
|
||||
XCTAssertEqual(helper.publisher.send(-1), .none) // ignored
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.value(3)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(17),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(22),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.schedule(options: nil)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(300))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.value(3),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(17),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(22),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.cancellableTokenDeinitCount, 2)
|
||||
}
|
||||
|
||||
func testFinishBeforeDue() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: scheduler,
|
||||
options: .nontrivialOptions)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
scheduler.rewind(to: .nanoseconds(4))
|
||||
helper.publisher.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.schedule(options: nil)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(100))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.completion(.failure(.oops))])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.cancellableTokenDeinitCount, 0)
|
||||
}
|
||||
|
||||
func testFailBeforeDue() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: scheduler,
|
||||
options: .nontrivialOptions)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
scheduler.rewind(to: .nanoseconds(4))
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.schedule(options: nil)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(100))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.schedule(options: nil)])
|
||||
XCTAssertEqual(scheduler.cancellableTokenDeinitCount, 0)
|
||||
}
|
||||
|
||||
func testCancelBeforeDue() throws {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: scheduler,
|
||||
options: .nontrivialOptions)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
scheduler.rewind(to: .nanoseconds(4))
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(100))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
XCTAssertEqual(scheduler.cancellableTokenDeinitCount, 0)
|
||||
}
|
||||
|
||||
func testDemand() throws {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: scheduler,
|
||||
options: .nontrivialOptions)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history, [])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(100))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(3))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
scheduler.rewind(to: .nanoseconds(200))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
scheduler.rewind(to: .nanoseconds(250))
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
scheduler.rewind(to: .nanoseconds(300))
|
||||
XCTAssertEqual(helper.publisher.send(4), .none)
|
||||
scheduler.rewind(to: .nanoseconds(350))
|
||||
XCTAssertEqual(helper.publisher.send(5), .none)
|
||||
scheduler.rewind(to: .nanoseconds(400))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(213),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(263),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(313),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(363),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
try XCTUnwrap(helper.downstreamSubscription).cancel()
|
||||
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(6), .none)
|
||||
scheduler.rewind(to: .nanoseconds(450))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(213),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(263),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(313),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(363),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
XCTAssertEqual(scheduler.cancellableTokenDeinitCount, 4)
|
||||
}
|
||||
|
||||
func testBadScheduler() {
|
||||
// What if the scheduler returns a cancellable that does nothing at all?
|
||||
|
||||
let scheduler = VirtualTimeScheduler(
|
||||
cancellableTokenType: VirtualTimeScheduler.NoopCancellableToken.self
|
||||
)
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: scheduler,
|
||||
options: .nontrivialOptions)
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
scheduler.rewind(to: .nanoseconds(2))
|
||||
XCTAssertEqual(helper.publisher.send(2), .none)
|
||||
scheduler.rewind(to: .nanoseconds(4))
|
||||
XCTAssertEqual(helper.publisher.send(3), .none)
|
||||
scheduler.rewind(to: .nanoseconds(6))
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(42), .none)
|
||||
scheduler.rewind(to: .nanoseconds(50))
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("Debounce"),
|
||||
.value(42),
|
||||
.value(42),
|
||||
.value(42)])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(scheduler.history,
|
||||
[.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(13),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(15),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(17),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions),
|
||||
.now,
|
||||
.minimumTolerance,
|
||||
.scheduleAfterDateWithInterval(.nanoseconds(19),
|
||||
interval: .nanoseconds(13),
|
||||
tolerance: .nanoseconds(7),
|
||||
options: .nontrivialOptions)])
|
||||
|
||||
XCTAssertEqual(scheduler.cancellableTokenDeinitCount, 0)
|
||||
}
|
||||
|
||||
func testSetupTimerWeakCapture() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var subscriptionDestroyed = false
|
||||
do {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .unlimited,
|
||||
receiveValueDemand: .none,
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13), scheduler: scheduler)
|
||||
}
|
||||
)
|
||||
|
||||
helper.tracking.onDeinit = { subscriptionDestroyed = true }
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(1), .none)
|
||||
}
|
||||
|
||||
XCTAssertTrue(subscriptionDestroyed)
|
||||
}
|
||||
|
||||
func testCrashesWithImmediateScheduler() {
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(2),
|
||||
receiveValueDemand: .max(1),
|
||||
createSut: {
|
||||
$0.debounce(for: .nanoseconds(13),
|
||||
scheduler: ImmediateScheduler.shared)
|
||||
}
|
||||
)
|
||||
|
||||
assertCrashes {
|
||||
_ = helper.publisher.send(1)
|
||||
}
|
||||
}
|
||||
|
||||
func testTimeoutReceiveValueBeforeSubscription() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
testReceiveValueBeforeSubscription(
|
||||
value: 42,
|
||||
expected: .crash,
|
||||
{ $0.debounce(for: .nanoseconds(13), scheduler: scheduler) }
|
||||
)
|
||||
}
|
||||
|
||||
func testTimeoutReceiveCompletionBeforeSubscription() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .crash,
|
||||
{ $0.debounce(for: .nanoseconds(13), scheduler: scheduler) }
|
||||
)
|
||||
}
|
||||
|
||||
func testTimeoutRequestBeforeSubscription() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
testRequestBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
shouldCrash: true,
|
||||
{ $0.debounce(for: .nanoseconds(13), scheduler: scheduler) }
|
||||
)
|
||||
}
|
||||
|
||||
func testTimeoutReceiveSubscriptionTwice() throws {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: nil,
|
||||
receiveValueDemand: .none,
|
||||
createSut: { $0.debounce(for: .nanoseconds(13), scheduler: scheduler) }
|
||||
)
|
||||
|
||||
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 testTimeoutCancelBeforeSubscription() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
testCancelBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
shouldCrash: false,
|
||||
{ $0.timeout(.nanoseconds(13), scheduler: scheduler) }
|
||||
)
|
||||
}
|
||||
|
||||
func testDebounceReflection() throws {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
|
||||
let customMirror = hasUpdatedReflection
|
||||
? expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("downstreamDemand", "max(0)"),
|
||||
("currentValue", "nil")
|
||||
)
|
||||
: expectedChildren(
|
||||
("upstream", .contains("CustomConnectablePublisherBase")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("upstreamSubscription", .anything),
|
||||
("downstreamDemand", "max(0)"),
|
||||
("currentValue", "nil")
|
||||
)
|
||||
|
||||
try testReflection(
|
||||
parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "Debounce",
|
||||
customMirror: customMirror,
|
||||
playgroundDescription: "Debounce",
|
||||
{ $0.debounce(for: .nanoseconds(13), scheduler: scheduler) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Remove this as soon as we switch our CI to the latest OS releases
|
||||
private var hasUpdatedReflection: Bool {
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
if #available(macOS 10.16, iOS 14.0, *) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
#else
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
@@ -372,7 +372,7 @@ final class DelayTests: XCTestCase {
|
||||
[.minimumTolerance,
|
||||
.now,
|
||||
.scheduleAfterDate(.seconds(0.35),
|
||||
tolerance: .nanoseconds(7),
|
||||
tolerance: 0,
|
||||
options: nil)])
|
||||
tracking.cancel()
|
||||
publisher.cancel()
|
||||
@@ -401,7 +401,7 @@ final class DelayTests: XCTestCase {
|
||||
[.minimumTolerance,
|
||||
.now,
|
||||
.scheduleAfterDate(.seconds(0.35),
|
||||
tolerance: .nanoseconds(7),
|
||||
tolerance: 0,
|
||||
options: nil)])
|
||||
tracking.cancel()
|
||||
publisher.cancel()
|
||||
@@ -445,7 +445,7 @@ final class DelayTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDelayReflection() throws {
|
||||
// Delay's Inner doesn't customize its reflection
|
||||
/// Delay's Inner doesn't customize its reflection
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: nil,
|
||||
|
||||
@@ -167,10 +167,10 @@ final class DropUntilOutputTests: XCTestCase {
|
||||
let dropUntilOutput = publisher.drop(untilOutputFrom: otherPublisher)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { _ in
|
||||
XCTAssertNil(publisher.subscriber)
|
||||
XCTAssertNil(otherPublisher.subscriber)
|
||||
XCTAssertNotNil(publisher.subscriber)
|
||||
XCTAssertNotNil(otherPublisher.subscriber)
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
XCTAssertEqual(otherSubscription.history, [])
|
||||
XCTAssertEqual(otherSubscription.history, [.requested(.max(1))])
|
||||
}
|
||||
)
|
||||
|
||||
@@ -220,15 +220,15 @@ final class DropUntilOutputTests: XCTestCase {
|
||||
|
||||
dropUntilOutput.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropUntilOutput"),
|
||||
.completion(.finished)])
|
||||
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, [.subscription("DropUntilOutput"),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(tracking.history, [.completion(.finished),
|
||||
.subscription("DropUntilOutput")])
|
||||
}
|
||||
|
||||
func testReusableOtherSubscriber() throws {
|
||||
@@ -285,7 +285,7 @@ final class DropUntilOutputTests: XCTestCase {
|
||||
func testDropUntilOutputOtherReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(
|
||||
value: 42,
|
||||
expected: .history([.subscription("DropUntilOutput"), .completion(.finished)],
|
||||
expected: .history([.completion(.finished), .subscription("DropUntilOutput")],
|
||||
demand: .none),
|
||||
{ Empty<Int, Never>().drop(untilOutputFrom: $0) }
|
||||
)
|
||||
@@ -294,8 +294,8 @@ final class DropUntilOutputTests: XCTestCase {
|
||||
func testDropUntilOutputReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.subscription("DropUntilOutput"),
|
||||
.completion(.finished),
|
||||
expected: .history([.completion(.finished),
|
||||
.subscription("DropUntilOutput"),
|
||||
.completion(.finished)]),
|
||||
{ $0.drop(untilOutputFrom: Empty<Int, Never>()) }
|
||||
)
|
||||
@@ -304,8 +304,8 @@ final class DropUntilOutputTests: XCTestCase {
|
||||
func testDropUntilOutputOtherReceiveCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.subscription("DropUntilOutput"),
|
||||
.completion(.finished),
|
||||
expected: .history([.completion(.finished),
|
||||
.subscription("DropUntilOutput"),
|
||||
.completion(.finished)]),
|
||||
{ Empty<Int, Never>().drop(untilOutputFrom: $0) }
|
||||
)
|
||||
|
||||
@@ -155,7 +155,7 @@ final class EncodeTests: XCTestCase {
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("finished", "false"),
|
||||
("upstreamSubscription", .anything)
|
||||
("upstreamSubscription", "nil")
|
||||
),
|
||||
playgroundDescription: "Encode",
|
||||
{ $0.encode(encoder: encoder) })
|
||||
@@ -275,7 +275,7 @@ final class EncodeTests: XCTestCase {
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("finished", "false"),
|
||||
("upstreamSubscription", .anything)
|
||||
("upstreamSubscription", "nil")
|
||||
),
|
||||
playgroundDescription: "Decode",
|
||||
{ $0.decode(type: Int.self, decoder: decoder) })
|
||||
|
||||
@@ -115,7 +115,7 @@ final class FlatMapTests: XCTestCase {
|
||||
// 1. FlatMap.Inner.receive(_ input:)
|
||||
// 2. Publisher.subscribe
|
||||
// ...
|
||||
// 3. FlatMap.Inner.ChildSubscriber.receive(subscription:)
|
||||
// 3. FlatMap.Inner.ChildSubscriber.recive(subscription:)
|
||||
// 4. subscription.request()
|
||||
// 5. Just.Inner.request()
|
||||
// 6. FlatMap.Inner.child(_:receivedValue)
|
||||
@@ -139,46 +139,35 @@ final class FlatMapTests: XCTestCase {
|
||||
flatMap.subscribe(downstreamSubscriber)
|
||||
XCTAssertEqual(upstreamPublisher.send(666), .none)
|
||||
|
||||
// Simply making it here shows that there's no deadlock
|
||||
// Simply making it here shows that there's no dealock
|
||||
}
|
||||
|
||||
func testCancelCancels() throws {
|
||||
func testCancelCancels() {
|
||||
let upstreamSubscription = CustomSubscription()
|
||||
let upstreamPublisher = CustomPublisherBase<Int, Never>(
|
||||
subscription: upstreamSubscription
|
||||
)
|
||||
subscription: upstreamSubscription)
|
||||
|
||||
let childSubscription = CustomSubscription()
|
||||
let childPublisher = CustomPublisherBase<Int, Never>(
|
||||
subscription: childSubscription
|
||||
)
|
||||
subscription: childSubscription)
|
||||
|
||||
let flatMap = upstreamPublisher.flatMap { _ in childPublisher }
|
||||
|
||||
var downstreamSubscription: Subscription?
|
||||
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: {
|
||||
downstreamSubscription = $0
|
||||
$0.request(.max(42))
|
||||
}
|
||||
)
|
||||
|
||||
upstreamSubscription.onCancel = {
|
||||
XCTAssertEqual(childSubscription.history, [.requested(.max(1)), .cancelled])
|
||||
}
|
||||
|
||||
childSubscription.onCancel = {
|
||||
XCTAssertEqual(upstreamSubscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(receiveSubscription:
|
||||
{
|
||||
downstreamSubscription = $0
|
||||
$0.request(.unlimited)
|
||||
})
|
||||
|
||||
flatMap.subscribe(downstreamSubscriber)
|
||||
|
||||
XCTAssertEqual(upstreamPublisher.send(1), .none)
|
||||
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
downstreamSubscription?.cancel()
|
||||
|
||||
XCTAssertEqual(upstreamSubscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(childSubscription.history, [.requested(.max(1)), .cancelled])
|
||||
XCTAssertEqual(upstreamSubscription.history.last, .cancelled)
|
||||
XCTAssertEqual(childSubscription.history.last, .cancelled)
|
||||
}
|
||||
|
||||
func testCancelTwice() throws {
|
||||
@@ -447,7 +436,10 @@ final class FlatMapTests: XCTestCase {
|
||||
let childSubscription = CustomSubscription()
|
||||
let child = CustomPublisher(subscription: childSubscription)
|
||||
|
||||
var recursionDepth = 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.subscription.onRequest = { _ in
|
||||
if recursionDepth <= 0 {
|
||||
return
|
||||
@@ -458,17 +450,9 @@ final class FlatMapTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(child), .none)
|
||||
|
||||
child.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription("FlatMap")])
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.max(1)),
|
||||
.requested(.max(1)),
|
||||
.requested(.max(1)),
|
||||
.requested(.max(1)),
|
||||
.requested(.max(1)),
|
||||
.requested(.max(1)),
|
||||
.requested(.max(1))])
|
||||
XCTAssertEqual(childSubscription.history, [.requested(.max(1))])
|
||||
assertCrashes {
|
||||
child.send(completion: .finished)
|
||||
}
|
||||
}
|
||||
|
||||
func testDownstreamLockReentrance() throws {
|
||||
@@ -965,9 +949,9 @@ final class FlatMapTests: XCTestCase {
|
||||
.requested(.max(1))])
|
||||
}
|
||||
|
||||
func testSendsSubscriptionDownstreamBeforeDemandUpstream() {
|
||||
func testSendsSubcriptionDownstreamBeforeDemandUpstream() {
|
||||
let sentDemandRequestUpstream = "Sent demand request upstream"
|
||||
let sentSubscriptionDownstream = "Sent subscription downstream"
|
||||
let sentSubscriptionDownstream = "Sent subcription downstream"
|
||||
var receiveOrder: [String] = []
|
||||
|
||||
let upstreamSubscription = CustomSubscription(onRequest: { _ in
|
||||
|
||||
@@ -98,8 +98,6 @@ final class FutureTests: XCTestCase {
|
||||
future.subscribe(subscriber)
|
||||
|
||||
subscriber.subscriptions.forEach { $0.cancel() }
|
||||
subscriber.subscriptions.forEach { $0.cancel() }
|
||||
subscriber.subscriptions.forEach { $0.request(.max(1)) }
|
||||
|
||||
promise?(.success(42))
|
||||
|
||||
@@ -108,7 +106,7 @@ final class FutureTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func testSubscribeAfterSuccessfulResolution() {
|
||||
func testSubscribeAfterResolution() {
|
||||
var promise: Sut.Promise?
|
||||
|
||||
let future = Sut { promise = $0 }
|
||||
@@ -126,36 +124,15 @@ final class FutureTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func testSubscribeAfterFailure() {
|
||||
// TODO: Remove this `if` as soon as iOS 14 is released.
|
||||
if hasMissingFailureAfterLateSubscriptionBug { return }
|
||||
|
||||
var promise: Sut.Promise?
|
||||
|
||||
let future = Sut { promise = $0 }
|
||||
promise?(.failure(.oops))
|
||||
|
||||
let subscriber = TrackingSubscriber()
|
||||
future.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Future"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
func testCrashesOnZeroDemand() throws {
|
||||
func testCrashesOnZeroDemand() {
|
||||
let future = Sut { _ in }
|
||||
|
||||
var downstreamSubscription: Subscription?
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
downstreamSubscription = $0
|
||||
let subscriber = TrackingSubscriber(receiveSubscription: { subscription in
|
||||
self.assertCrashes {
|
||||
subscription.request(.none)
|
||||
}
|
||||
)
|
||||
})
|
||||
future.subscribe(subscriber)
|
||||
|
||||
try self.assertCrashes {
|
||||
try XCTUnwrap(downstreamSubscription).request(.none)
|
||||
}
|
||||
}
|
||||
|
||||
func testValueRecursion() {
|
||||
@@ -234,7 +211,7 @@ final class FutureTests: XCTestCase {
|
||||
XCTAssertTrue(hasStarted)
|
||||
}
|
||||
|
||||
func testWaitsForDemandSuccess() {
|
||||
func testWaitsForRequest() {
|
||||
var promise: Sut.Promise?
|
||||
|
||||
let future = Sut { promise = $0 }
|
||||
@@ -259,105 +236,4 @@ final class FutureTests: XCTestCase {
|
||||
.completion(.finished)
|
||||
])
|
||||
}
|
||||
|
||||
func testReleasesEverythingOnTermination() {
|
||||
|
||||
enum TerminationReason: CaseIterable {
|
||||
case cancelled
|
||||
case finished
|
||||
case failed
|
||||
}
|
||||
|
||||
for reason in TerminationReason.allCases {
|
||||
weak var weakSubscriber: TrackingSubscriber?
|
||||
weak var weakFuture: Sut?
|
||||
weak var weakSubscription: AnyObject?
|
||||
|
||||
do {
|
||||
var promise: Sut.Promise?
|
||||
let future = Sut { promise = $0 }
|
||||
do {
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
weakSubscription = $0 as AnyObject
|
||||
$0.request(.max(1))
|
||||
}
|
||||
)
|
||||
weakSubscriber = subscriber
|
||||
weakFuture = future
|
||||
|
||||
future.subscribe(subscriber)
|
||||
}
|
||||
|
||||
switch reason {
|
||||
case .cancelled:
|
||||
(weakSubscription as? Subscription)?.cancel()
|
||||
case .finished:
|
||||
promise?(.success(1))
|
||||
case .failed:
|
||||
promise?(.failure(.oops))
|
||||
}
|
||||
|
||||
XCTAssertNil(weakSubscriber, "Subscriber leaked - \(reason)")
|
||||
if !leaksSubscription {
|
||||
// This leak has been fixed in the betas.
|
||||
// TODO: Remove this `if` as soon as iOS 14 is released.
|
||||
XCTAssertNil(weakSubscription, "Subscription leaked - \(reason)")
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertNil(weakFuture, "Future leaked - \(reason)")
|
||||
}
|
||||
}
|
||||
|
||||
func testConduitReflection() throws {
|
||||
try testSubscriptionReflection(
|
||||
description: "Future",
|
||||
customMirror: expectedChildren(
|
||||
("parent", .contains("Future")),
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("hasAnyDemand", "false"),
|
||||
("subject", .contains("Future"))
|
||||
),
|
||||
playgroundDescription: "Future",
|
||||
sut: Sut { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS, deprecated: 10.16, message: """
|
||||
If macOS 10.16/11.0 has already been released, this property should be removed
|
||||
""")
|
||||
@available(iOS, deprecated: 14, message: """
|
||||
If iOS 14 has already been released, this property should be removed
|
||||
""")
|
||||
private var leaksSubscription: Bool { // swiftlint:disable:this let_var_whitespace
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
if #available(macOS 10.16, iOS 14.0, *) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
@available(macOS, deprecated: 10.16, message: """
|
||||
If macOS 10.16/11.0 has already been released, this property should be removed
|
||||
""")
|
||||
@available(iOS, deprecated: 14, message: """
|
||||
If iOS 14 has already been released, this property should be removed
|
||||
""")
|
||||
private var hasMissingFailureAfterLateSubscriptionBug: Bool {
|
||||
// swiftlint:disable:previous let_var_whitespace
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
if #available(macOS 10.16, iOS 14.0, *) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ final class IgnoreOutputTests: XCTestCase {
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testSendsSubscriptionDownstreamBeforeDemandUpstream() {
|
||||
func testSendsSubcriptionDownstreamBeforeDemandUpstream() {
|
||||
var didReceiveSubscription = false
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Error>(subscription: subscription)
|
||||
@@ -208,7 +208,7 @@ final class IgnoreOutputTests: XCTestCase {
|
||||
description: "IgnoreOutput",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriberBase")),
|
||||
("status", .anything)
|
||||
("status", .contains("awaitingSubscription"))
|
||||
),
|
||||
playgroundDescription: "IgnoreOutput",
|
||||
{ $0.ignoreOutput() })
|
||||
|
||||
@@ -210,7 +210,7 @@ final class JustTests: XCTestCase {
|
||||
XCTAssertEqual(Sut("f").first(), Sut("f"))
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecialization() {
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(Sut<Int>(42).first { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).first { $0 != 42 }, .init(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).first { $0 < 0 }, .init(nil))
|
||||
@@ -221,7 +221,7 @@ final class JustTests: XCTestCase {
|
||||
XCTAssertEqual(Sut("g").last(), Sut("g"))
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecialization() {
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(Sut<Int>(42).last { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).last { $0 != 42 }, .init(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).last { $0 < 0 }, .init(nil))
|
||||
|
||||
@@ -185,7 +185,6 @@ final class MapErrorTests: XCTestCase {
|
||||
description: "MapError",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "MapError",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.mapError { $0 } })
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ final class MapKeyPathTests: XCTestCase {
|
||||
}
|
||||
|
||||
MapTests.testEmpty(valueComparator: ==) {
|
||||
$0.map(\.doubled, \.tripled, \.quadrupled)
|
||||
$0.map(\.doubled, \.tripled, \.quadripled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ final class MapKeyPathTests: XCTestCase {
|
||||
}
|
||||
|
||||
MapTests.testError(valueComparator: ==) {
|
||||
$0.map(\.doubled, \.tripled, \.quadrupled)
|
||||
$0.map(\.doubled, \.tripled, \.quadripled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@ final class MapKeyPathTests: XCTestCase {
|
||||
{ $0.map(\.doubled, \.tripled) })
|
||||
|
||||
MapTests.testRange(valueComparator: ==,
|
||||
mapping: { ($0.doubled, $0.tripled, $0.quadrupled) },
|
||||
{ $0.map(\.doubled, \.tripled, \.quadrupled) })
|
||||
mapping: { ($0.doubled, $0.tripled, $0.quadripled) },
|
||||
{ $0.map(\.doubled, \.tripled, \.quadripled) })
|
||||
}
|
||||
|
||||
func testNoDemand() {
|
||||
MapTests.testNoDemand { $0.map(\.doubled) }
|
||||
MapTests.testNoDemand { $0.map(\.doubled, \.tripled) }
|
||||
MapTests.testNoDemand { $0.map(\.doubled, \.tripled, \.quadrupled) }
|
||||
MapTests.testNoDemand { $0.map(\.doubled, \.tripled, \.quadripled) }
|
||||
}
|
||||
|
||||
func testRequestDemandOnSubscribe() {
|
||||
@@ -74,14 +74,14 @@ final class MapKeyPathTests: XCTestCase {
|
||||
}
|
||||
|
||||
MapTests.testRequestDemandOnSubscribe {
|
||||
$0.map(\.doubled, \.tripled, \.quadrupled)
|
||||
$0.map(\.doubled, \.tripled, \.quadripled)
|
||||
}
|
||||
}
|
||||
|
||||
func testDemandOnReceive() {
|
||||
MapTests.testDemandOnReceive { $0.map(\.doubled) }
|
||||
MapTests.testDemandOnReceive { $0.map(\.doubled, \.tripled) }
|
||||
MapTests.testDemandOnReceive { $0.map(\.doubled, \.tripled, \.quadrupled) }
|
||||
MapTests.testDemandOnReceive { $0.map(\.doubled, \.tripled, \.quadripled) }
|
||||
}
|
||||
|
||||
func testCompletion() {
|
||||
@@ -94,27 +94,27 @@ final class MapKeyPathTests: XCTestCase {
|
||||
}
|
||||
|
||||
MapTests.testCompletion(valueComparator: ==) {
|
||||
$0.map(\.doubled, \.tripled, \.quadrupled)
|
||||
$0.map(\.doubled, \.tripled, \.quadripled)
|
||||
}
|
||||
}
|
||||
|
||||
func testCancel() throws {
|
||||
try MapTests.testCancel { $0.map(\.doubled) }
|
||||
try MapTests.testCancel { $0.map(\.doubled, \.tripled) }
|
||||
try MapTests.testCancel { $0.map(\.doubled, \.tripled, \.quadrupled) }
|
||||
try MapTests.testCancel { $0.map(\.doubled, \.tripled, \.quadripled) }
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
try MapTests.testCancelAlreadyCancelled {
|
||||
try MapTests.testCancelAldreadyCancelled {
|
||||
$0.map(\.doubled)
|
||||
}
|
||||
|
||||
try MapTests.testCancelAlreadyCancelled {
|
||||
try MapTests.testCancelAldreadyCancelled {
|
||||
$0.map(\.doubled, \.tripled)
|
||||
}
|
||||
|
||||
try MapTests.testCancelAlreadyCancelled {
|
||||
$0.map(\.doubled, \.tripled, \.quadrupled)
|
||||
try MapTests.testCancelAldreadyCancelled {
|
||||
$0.map(\.doubled, \.tripled, \.quadripled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,6 @@ final class MapKeyPathTests: XCTestCase {
|
||||
("keyPath", .contains("KeyPath"))
|
||||
),
|
||||
playgroundDescription: "ValueForKey",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.map(\.doubled) })
|
||||
|
||||
try testReflection(parentInput: Int.self,
|
||||
@@ -137,7 +136,6 @@ final class MapKeyPathTests: XCTestCase {
|
||||
("keyPath1", .contains("KeyPath"))
|
||||
),
|
||||
playgroundDescription: "ValueForKeys",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.map(\.doubled, \.tripled) })
|
||||
|
||||
try testReflection(parentInput: Int.self,
|
||||
@@ -149,8 +147,7 @@ final class MapKeyPathTests: XCTestCase {
|
||||
("keyPath2", .contains("KeyPath"))
|
||||
),
|
||||
playgroundDescription: "ValueForKeys",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.map(\.doubled, \.tripled, \.quadrupled) })
|
||||
{ $0.map(\.doubled, \.tripled, \.quadripled) })
|
||||
}
|
||||
|
||||
func testMapKeyPathReceiveValueBeforeSubscription() {
|
||||
@@ -169,7 +166,7 @@ final class MapKeyPathTests: XCTestCase {
|
||||
expected: .history([.value((0, 0, 0))],
|
||||
demand: .max(42),
|
||||
comparator: ==),
|
||||
{ $0.map(\.doubled, \.tripled, \.quadrupled) })
|
||||
{ $0.map(\.doubled, \.tripled, \.quadripled) })
|
||||
}
|
||||
|
||||
func testMapKeyPathReceiveCompletionBeforeSubscription() {
|
||||
@@ -188,7 +185,7 @@ final class MapKeyPathTests: XCTestCase {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.completion(.finished)], comparator: ==),
|
||||
{ $0.map(\.doubled, \.tripled, \.quadrupled) }
|
||||
{ $0.map(\.doubled, \.tripled, \.quadripled) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -202,7 +199,7 @@ final class MapKeyPathTests: XCTestCase {
|
||||
}
|
||||
|
||||
try testLifecycle(sendValue: 31, cancellingSubscriptionReleasesSubscriber: true) {
|
||||
$0.map(\.doubled, \.tripled, \.quadrupled)
|
||||
$0.map(\.doubled, \.tripled, \.quadripled)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,5 +209,5 @@ extension Int {
|
||||
|
||||
fileprivate var tripled: Int { return self * 3 }
|
||||
|
||||
fileprivate var quadrupled: Int { return self * 4 }
|
||||
fileprivate var quadripled: Int { return self * 4 }
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ final class MapTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testMapCancelAlreadyCancelled() throws {
|
||||
try MapTests.testCancelAlreadyCancelled { $0.map { $0 * 2 } }
|
||||
try MapTests.testCancelAldreadyCancelled { $0.map { $0 * 2 } }
|
||||
}
|
||||
|
||||
func testTryMapCancelAlreadyCancelled() throws {
|
||||
@@ -221,7 +221,6 @@ final class MapTests: XCTestCase {
|
||||
description: "Map",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "Map",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.map { $0 * 2 } })
|
||||
}
|
||||
|
||||
@@ -608,7 +607,7 @@ final class MapTests: XCTestCase {
|
||||
line: line)
|
||||
}
|
||||
|
||||
static func testCancelAlreadyCancelled<Map: Publisher>(
|
||||
static func testCancelAldreadyCancelled<Map: Publisher>(
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line,
|
||||
_ map: (CustomPublisher) -> Map
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ final class OptionalPublisherTests: XCTestCase {
|
||||
"comparator should not be called for removeDuplicates(by:)")
|
||||
}
|
||||
|
||||
func testAllSatisfyOperatorSpecialization() {
|
||||
func testAllSatifyOperatorSpecialization() {
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
@@ -218,7 +218,7 @@ final class OptionalPublisherTests: XCTestCase {
|
||||
XCTAssertEqual(Sut<Int>(nil).first(), Sut(nil))
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecialization() {
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
@@ -234,7 +234,7 @@ final class OptionalPublisherTests: XCTestCase {
|
||||
XCTAssertEqual(Sut<Int>(nil).last(), Sut(nil))
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecialization() {
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ final class ReceiveOnTests: XCTestCase {
|
||||
helper.publisher.send(completion: .finished)
|
||||
}
|
||||
|
||||
func testStrongCaptureWhenSchedulingValue() {
|
||||
func testWeakCaptureWhenSchedulingValue() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var value: Int?
|
||||
var subscriberReleased = false
|
||||
@@ -276,11 +276,11 @@ final class ReceiveOnTests: XCTestCase {
|
||||
}
|
||||
XCTAssertFalse(subscriberReleased)
|
||||
scheduler.executeScheduledActions()
|
||||
XCTAssertEqual(value, 42)
|
||||
XCTAssertNil(value)
|
||||
XCTAssertTrue(subscriberReleased)
|
||||
}
|
||||
|
||||
func testStrongCaptureWhenSchedulingCompletion() {
|
||||
func testWeakCaptureWhenSchedulingCompletion() {
|
||||
let scheduler = VirtualTimeScheduler()
|
||||
var completion: Subscribers.Completion<TestingError>?
|
||||
var subscriberReleased = false
|
||||
@@ -300,7 +300,7 @@ final class ReceiveOnTests: XCTestCase {
|
||||
}
|
||||
XCTAssertFalse(subscriberReleased)
|
||||
scheduler.executeScheduledActions()
|
||||
XCTAssertEqual(completion, .finished)
|
||||
XCTAssertNil(completion)
|
||||
XCTAssertTrue(subscriberReleased)
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ final class ReduceTests: XCTestCase {
|
||||
{ $0.tryReduce(1, *) })
|
||||
}
|
||||
|
||||
func testTryReduceFailureBecauseOfThrow() throws {
|
||||
func testTryReduceFailureBecausOfThrow() throws {
|
||||
|
||||
func reducer(_ accumulator: Int, _ newValue: Int) throws -> Int {
|
||||
if newValue == 5 {
|
||||
|
||||
@@ -108,7 +108,6 @@ final class ReplaceNilTests: XCTestCase {
|
||||
description: "Map",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "Map",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.replaceNil(with: 0) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ final class ResultPublisherTests: XCTestCase {
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testAllSatisfyOperatorSpecialization() {
|
||||
func testAllSatifyOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
XCTAssertEqual(Sut<Int>(0).allSatisfy(predicate).result, .success(false))
|
||||
@@ -288,7 +288,7 @@ final class ResultPublisherTests: XCTestCase {
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryAllSatisfyOperatorSpecialization() {
|
||||
func testTryAllSatifyOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
|
||||
@@ -152,7 +152,6 @@ final class ScanTests: XCTestCase {
|
||||
("result", "0")
|
||||
),
|
||||
playgroundDescription: "Scan",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.scan(0, +) })
|
||||
}
|
||||
|
||||
@@ -314,7 +313,7 @@ final class ScanTests: XCTestCase {
|
||||
description: "TryScan",
|
||||
customMirror: expectedChildren(
|
||||
("downstream", .contains("TrackingSubscriber")),
|
||||
("status", .anything),
|
||||
("status", .contains("awaitingSubscription")),
|
||||
("result", "0")
|
||||
),
|
||||
playgroundDescription: "TryScan",
|
||||
|
||||
@@ -362,7 +362,7 @@ final class SequenceTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecialization() {
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(makePublisher(1 ..< 9).first { $0.isMultiple(of: 4) }, .init(4))
|
||||
XCTAssertEqual(makePublisher(1 ..< 9).first { $0.isMultiple(of: 13) }, .init(nil))
|
||||
XCTAssertEqual(
|
||||
@@ -531,7 +531,7 @@ final class SequenceTests: XCTestCase {
|
||||
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).last(), .init(nil))
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecialization() {
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(makePublisher(1 ..< 9).last { $0.isMultiple(of: 4) }, .init(8))
|
||||
XCTAssertEqual(makePublisher(1 ..< 9).last { $0.isMultiple(of: 13) }, .init(nil))
|
||||
XCTAssertEqual(
|
||||
@@ -540,7 +540,7 @@ final class SequenceTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testPrependVariadicOperatorSpecialization() {
|
||||
func testPrependVariadicOperatorSpezialization() {
|
||||
let baseCollection = TrackingCollection<Int>([4, 5, 6, 7])
|
||||
|
||||
XCTAssertEqual(baseCollection.history, [.initFromSequence])
|
||||
@@ -613,7 +613,7 @@ final class SequenceTests: XCTestCase {
|
||||
XCTAssertEqual(newCollection.history, [.initFromSequence, .appendSequence])
|
||||
}
|
||||
|
||||
func testAppendVariadicOperatorSpecialization() {
|
||||
func testAppendVariadicOperatorSpezialization() {
|
||||
let baseCollection = TrackingCollection<Int>([1, 2, 3])
|
||||
|
||||
XCTAssertEqual(baseCollection.history, [.initFromSequence])
|
||||
|
||||
@@ -180,7 +180,6 @@ final class SetFailureTypeTests: XCTestCase {
|
||||
description: "SetFailureType",
|
||||
customMirror: childrenIsEmpty,
|
||||
playgroundDescription: "SetFailureType",
|
||||
subscriberIsAlsoSubscription: false,
|
||||
{ $0.setFailureType(to: Error.self) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,17 +134,15 @@ final class ShareTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testShareReceiveValueBeforeSubscription() {
|
||||
testReceiveValueBeforeSubscription(
|
||||
value: 0,
|
||||
expected: .history([.subscription("Multicast")], demand: .none),
|
||||
{ $0.share() }
|
||||
)
|
||||
testReceiveValueBeforeSubscription(value: 0,
|
||||
expected: .crash,
|
||||
{ $0.share() })
|
||||
}
|
||||
|
||||
func testShareCompletionBeforeSubscription() {
|
||||
testReceiveCompletionBeforeSubscription(
|
||||
inputType: Int.self,
|
||||
expected: .history([.subscription("Multicast")]),
|
||||
expected: .crash,
|
||||
{ $0.share() }
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user