Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26e86a9905 | |||
| bab8e08d2f | |||
| 4060ee9f57 | |||
| 5996772433 | |||
| cd45c77fac | |||
| e618d179fe | |||
| 4fa5f48c19 | |||
| 28993ae57d | |||
| 3d61bf87e7 | |||
| 911a4e1aa3 | |||
| beb38dec0e | |||
| 1fbf688897 | |||
| 5436868053 | |||
| 4977ca158f | |||
| 96214ac5f9 | |||
| 21fda909f5 | |||
| 8438d09b82 | |||
| 30a60b52cc | |||
| a93ed143fb | |||
| e054a884ef | |||
| 9bba508134 | |||
| 2d857d6d66 | |||
| 7286336b28 | |||
| 8a0bb6f846 | |||
| 142811c500 | |||
| 3b1cff9337 | |||
| efb4369c74 | |||
| 9d87a3b4ea | |||
| 4714b80631 | |||
| 3cba7363b4 | |||
| b2f592611d | |||
| 8786d0860a | |||
| 9100ccafb3 | |||
| 3fa048ddca | |||
| 8832baa05b | |||
| 11fdf7eaf3 | |||
| 059a86d393 | |||
| c1b4d93a0f | |||
| fb184ceebe | |||
| 69b50074ff | |||
| 7351cd671c | |||
| 26e112f894 | |||
| 6c732515d8 | |||
| 6892923743 | |||
| f8809ffac4 | |||
| 5da402bb2c | |||
| e962ce1e3b | |||
| 19df744bf1 | |||
| 69cc5a92c2 | |||
| cb22cc98f2 | |||
| 22cf5b69ba | |||
| a3fecd18e6 | |||
| 48c6f2999b | |||
| 91297ae63a | |||
| 6f61dcc083 | |||
| 24eca2fab8 | |||
| 9b3c36124f | |||
| 1b017e1dfc | |||
| 32b3aeb94c | |||
| 579d174288 | |||
| 1c850fc6bb | |||
| b35ecf8356 | |||
| 302f663a3f |
+174
-122
@@ -1,3 +1,103 @@
|
||||
macOS_tests_steps: &macOS_tests_steps
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: |
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-code-coverage --build-path .build-test-debug"
|
||||
xcrun llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests \
|
||||
> coverage.txt
|
||||
- run:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: |
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-release"
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
- run:
|
||||
name: Building for testing on macOS with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-sdk macosx \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing on macOS with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-sdk macosx \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
- store_artifacts:
|
||||
path: xcodebuild_test-without-building.log
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
ubuntu_tests_steps: &ubuntu_tests_steps
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Installing dependencies
|
||||
command: |
|
||||
apt update -y
|
||||
apt upgrade -y
|
||||
apt install -y curl python3.8
|
||||
- run:
|
||||
name: "Generating LinuxMain.swift"
|
||||
command: python3.8 utils/discover_tests.py
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: |
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-code-coverage \
|
||||
--disable-index-store \
|
||||
--build-path .build-test-debug"
|
||||
llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest \
|
||||
> coverage.txt
|
||||
- run:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: |
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_TEST_FLAGS="--disable-index-store \
|
||||
--build-path .build-test-debug-sanitize-thread" \
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-release"
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
"Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
@@ -5,93 +105,51 @@ jobs:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1.3"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: |
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-code-coverage --build-path .build-test-debug"
|
||||
xcrun llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests \
|
||||
> coverage.txt
|
||||
- run:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: |
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-release"
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
- run:
|
||||
name: Building for testing on macOS 10.15.0 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-sdk macosx10.15 \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing on macOS 10.15.0 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-sdk macosx10.15 \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
- store_artifacts:
|
||||
path: xcodebuild_test-without-building.log
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
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)":
|
||||
<<: *macOS_tests_steps
|
||||
|
||||
"Execute tests on macOS 10.15.0 (Xcode 12.1.0, Swift 5.3.0)":
|
||||
macos:
|
||||
xcode: "11.6.0"
|
||||
xcode: "12.1.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.2.4"
|
||||
SWIFT_VERSION: "5.3.0"
|
||||
<<: *macOS_tests_steps
|
||||
|
||||
"Execute tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)":
|
||||
macos:
|
||||
xcode: "12.5.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.4.0"
|
||||
<<: *macOS_tests_steps
|
||||
|
||||
"Execute compatibility tests on iOS 14.5 (Xcode 12.5.0, Swift 5.4.0)":
|
||||
macos:
|
||||
xcode: "12.5.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.4.0"
|
||||
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 14.5 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 12,OS=14.5" \
|
||||
-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 14.5 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 12,OS=14.5" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
@@ -100,6 +158,18 @@ jobs:
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
|
||||
"Execute compatibility tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)":
|
||||
macos:
|
||||
xcode: "12.5.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.4.0"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Testing against Combine on macOS 11.4.0
|
||||
command: |
|
||||
make test-compatibility
|
||||
|
||||
"Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)":
|
||||
macos:
|
||||
xcode: "10.2.1"
|
||||
@@ -161,69 +231,44 @@ jobs:
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.0)":
|
||||
docker:
|
||||
- image: swift:5.0-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.0"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.1)":
|
||||
docker:
|
||||
- image: swift:5.1-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.2)":
|
||||
docker:
|
||||
- image: swift:5.2-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.2"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Installing dependencies
|
||||
command: |
|
||||
apt update -y
|
||||
apt upgrade -y
|
||||
apt install -y curl
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: | # We need to run the test command twice because of https://bugs.swift.org/browse/SR-10783
|
||||
make test-debug \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--enable-code-coverage \
|
||||
--build-path .build-test-debug" \
|
||||
> /dev/null 2>&1 \
|
||||
|| true
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--enable-code-coverage \
|
||||
--build-path .build-test-debug"
|
||||
llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest \
|
||||
> coverage.txt
|
||||
- run:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: | # We need to run the test command twice because of https://bugs.swift.org/browse/SR-10783
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--build-path .build-test-debug-sanitize-thread" \
|
||||
> /dev/null 2>&1 \
|
||||
|| true
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--build-path .build-test-debug-sanitize-thread" \
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-test-discovery \
|
||||
--enable-index-store \
|
||||
--build-path .build-test-release"
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.3)":
|
||||
docker:
|
||||
- image: swift:5.3-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.3"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.4)":
|
||||
docker:
|
||||
- image: swift:5.4-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.4"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Run SwiftLint and Danger":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
xcode: "11.4.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
@@ -242,7 +287,7 @@ jobs:
|
||||
|
||||
"Run Pod spec lint":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
xcode: "11.4.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
@@ -257,15 +302,22 @@ workflows:
|
||||
"OpenCombine: execute tests on macOS":
|
||||
jobs:
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)"
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 12.1.0, Swift 5.3.0)"
|
||||
- "Execute tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)"
|
||||
"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 14.5 (Xcode 12.5.0, Swift 5.4.0)"
|
||||
- "Execute compatibility tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)"
|
||||
"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.0)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.1)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.2)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.3)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.4)"
|
||||
"OpenCombine: run SwiftLint and Danger":
|
||||
jobs:
|
||||
- "Run SwiftLint and Danger"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: SwiftWasm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
carton_wasmer_test:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: swiftwasm/swiftwasm-action@v5.3
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
/.swiftpm
|
||||
Tests/LinuxMain.swift
|
||||
|
||||
# Created by https://www.gitignore.io/api/Xcode
|
||||
# Edit at https://www.gitignore.io/?templates=Xcode
|
||||
|
||||
@@ -2,6 +2,8 @@ included:
|
||||
- Sources
|
||||
- Tests
|
||||
|
||||
child_config: Tests/.swiftlint.yml
|
||||
|
||||
disabled_rules:
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
# 0.12.0 (29 Jan 2021)
|
||||
|
||||
This release adds a new `OpenCombineShim` product that will conditionally re-export either
|
||||
Combine on Apple platforms, or OpenCombine on other platforms. Additionally, `ObservableObject`
|
||||
protocol is now available and working on all platforms.
|
||||
|
||||
A bug with `Timer(timeInterval:repeats:block:)` firing immediately not accounting for the passed
|
||||
`timeInterval` is fixed.
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix `Timer(timeInterval:repeats:block:)` not accounting `timeInterval` ([#196](https://github.com/OpenCombine/OpenCombine/pull/196)) via [@grigorye](https://github.com/grigorye)
|
||||
- Add `OpenCombineShim` product for easier importing ([#197](https://github.com/OpenCombine/OpenCombine/pull/197)) via [@MaxDesiatov](https://github.com/MaxDesiatov)
|
||||
- Implementation for `ObservableObject` with `Mirror` ([#201](https://github.com/OpenCombine/OpenCombine/pull/201)) via [@kateinoigakukun](https://github.com/kateinoigakukun)
|
||||
|
||||
# 0.11.0 (29 Oct 2020)
|
||||
|
||||
This release is compatible with Xcode 12.1.
|
||||
|
||||
### Additions
|
||||
- `Publisher.assigned(to:)` method that accepts a `Published.Publisher`.
|
||||
- New `Publisher.switchToLatest()` overloads.
|
||||
- New `Publisher.flatMap(maxPublishers:_:)` overloads.
|
||||
- `Optional.publisher` property.
|
||||
- New `_Introspection` protocol that allows to track and explore the subscription graph and data flow.
|
||||
|
||||
### Bugfixes
|
||||
- The project should now compile without warnings.
|
||||
- The following entities have been updated to match the behavior of the newest Combine version:
|
||||
- `Subscribers.Assign`
|
||||
- `Publishers.Breakpoint`
|
||||
- `Publishers.Buffer`
|
||||
- `CombineIdentifier`
|
||||
- `Publishers.CompactMap`
|
||||
- `Publishers.Concatenate`
|
||||
- `Publishers.Debounce`
|
||||
- `Publishers.Delay`
|
||||
- `DispatchQueue.SchedulerTimeType.Stride`
|
||||
- `Publishers.Drop`
|
||||
- `Publishers.Encode`
|
||||
- `Publishers.Decode`
|
||||
- `Publishers.Filter`
|
||||
- `Publishers.HandleEvents`
|
||||
- `Publishers.IgnoreOutput`
|
||||
- `Publishers.MeasureInterval`
|
||||
- `OperationQueue` scheduler
|
||||
- `Published`
|
||||
- `Publishers.ReceiveOn`
|
||||
- `Publishers.ReplaceError`
|
||||
- `RunLoop scheduler`
|
||||
- `Publishers.Sequence`
|
||||
- `Subscribers.Sink`
|
||||
- `Publishers.SubscribeOn`
|
||||
- `Publishers.Timeout`
|
||||
- `Timer` publisher
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.10.2 (23 Oct 2020)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed a crash caused by recursive acquisition of a non-recursive lock in SubbjectSubscriber (#186, thanks @stuaustin for the bug report)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.10.1 (4 Oct 2020)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed build errors on Linux with Swift 5.0 and Swift 5.3 toolchains (thanks, @adamleonard and @devmaximilian)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.10.0 (28 Jun 2020)
|
||||
|
||||
This release is compatible with Xcode 11.5.
|
||||
|
||||
### Additions
|
||||
- `Timer.publish(every:tolerance:on:in:options:)` (#156, thank you @MaxDesiatov)
|
||||
- `OperationQueue` scheduler (#165)
|
||||
- `Publishers.Timeout` (#164)
|
||||
- `Publishers.Debounce` (#133)
|
||||
|
||||
### Bugfixes
|
||||
- `PassthroughSubject`, `CurrentValueSubject` and `Future` have been rewritten from scratch. They are now faster, more correct and no longer leak subscriptions (#170).
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.9.0 (12 Jun 2020)
|
||||
|
||||
This release is compatible with Xcode 11.5.
|
||||
|
||||
### Additions
|
||||
- The `Subscribers.Demand` struct can be nicely formatted in LLDB (#146, thank you @mayoff).
|
||||
- `Publishers.SwitchToLatest` (#142).
|
||||
- The `RunLoop` scheduler in `OpenCombineFoundation` (#131).
|
||||
- `Publishers.Catch` and `Publishers.TryCatch` (#140).
|
||||
|
||||
### Bugfixes
|
||||
- Worked around a [bug in the Swift compiler](https://bugs.swift.org/browse/SR-11680) when building the `COpenCombineHelpers` target (#145, thank you @mayoff).
|
||||
- Improved documentation.
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.8.0 (17 Jan 2020)
|
||||
|
||||
This release is compatible with Xcode 11.3.1.
|
||||
|
||||
### Additions
|
||||
- `Publishers.ReplaceEmpty` (#122, thank you @spadafiva)
|
||||
- `NotificationCenter.Publisher` (#84)
|
||||
- `URLSession.DataTaskPublisher` (#127)
|
||||
- `Publishers.DropUntilOutput` (#136)
|
||||
- `Publishers.CollectByCount` (#137)
|
||||
- `Publishers.AssertNoFailure` (#138)
|
||||
- `Publishers.Buffer` (#143)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed integer overflows in `DispatchQueue.SchedulerTimeType.Stride` (#126, #130)
|
||||
- Fixed the 'default will never be executed' warning on non-Darwin platforms (like Linux) (#129)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
|
||||
|
||||
# 0.7.0 (10 Dec 2019)
|
||||
|
||||
This release is compatible with Xcode 11.2.1.
|
||||
|
||||
### Additions
|
||||
- `Publishers.Delay` (#114)
|
||||
- `Publishers.ReceiveOn` (#115)
|
||||
- `Publishers.SubscribeOn` (#116)
|
||||
- `Publishers.MeasureInterval` (#117)
|
||||
- `Publishers.Breakpoint` (#118)
|
||||
- `Publishers.HandleEvents` (#118)
|
||||
- `Publishers.Concatenate` (#90)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
|
||||
|
||||
# 0.6.0 (26 Nov 2019)
|
||||
|
||||
This release is compatible with Xcode 11.2.1.
|
||||
|
||||
### Thread safety
|
||||
- `Publishers.IgnoreOutput` has been audited for thread safety (#88)
|
||||
- `Publishers.DropWhile` and `Publishers.TryDropWhile` have been audited for thread safety (#87)
|
||||
|
||||
### Additions
|
||||
- `Publishers.Output` (#91)
|
||||
- `Record` (#100)
|
||||
- `Publishers.RemoveDuplicates`, `Publishers.TryRemoveDuplicates` (#89)
|
||||
- `Publishers.PrefixWhile`, `Publishers.TryPrefixWhile` (#89)
|
||||
- `Future` (#107, thanks @MaxDesiatov!)
|
||||
|
||||
### Bugfixes
|
||||
- The behavior of the `Publishers.Encode` and `Publishers.Decode` subscriptions is fixed (#112)
|
||||
- The behavior of the `Publishers.IgnoreOutput` subscription is fixed (#88)
|
||||
- The behavior of the `Publishers.Print` subscription is fixed (#92)
|
||||
- The behavior of the `Publishers.ReplaceError` subscription is fixed (#89)
|
||||
- The behavior of the `Publishers.Filter` and `Publishers.TryFilter` subscriptions is fixed (#89)
|
||||
- The behavior of the `Publishers.CompactMap` and `Publishers.TryCompactMap` subscriptions is fixed (#89)
|
||||
- The behavior of the `Publishers.Multicast` subscription is fixed (#110)
|
||||
- `Publishers.FlatMap` is reimplemented from scratch. Its behavior is fixed in many ways, it now fully matches that of Combine (#89)
|
||||
- `@Published` property wrapper is fixed! (#112)
|
||||
- The behavior of `DispatchQueue.SchedulerTimeType` is fixed to match that of the latest SDKs (#96)
|
||||
- OpenCombine is now usable on 32 bit platforms. Why? Because we can.
|
||||
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
|
||||
|
||||
# 0.5.0 (17 Oct 2019)
|
||||
|
||||
This release is compatible with Xcode 11.1.
|
||||
|
||||
### Additions
|
||||
- `Publishers.MapKeyPath` (#71)
|
||||
- `Publishers.Reduce` (#76)
|
||||
- `Publishers.TryReduce` (#76)
|
||||
- `Publishers.Last` (#76)
|
||||
- `Publishers.LastWhere` (#76)
|
||||
- `Publishers.TryLastWhere` (#76)
|
||||
- `Publishers.AllSatisfy` (#76)
|
||||
- `Publishers.TryAllSatisfy` (#76)
|
||||
- `Publishers.Contains` (#76)
|
||||
- `Publishers.ContainsWhere` (#76)
|
||||
- `Publishers.TryContainsWhere` (#76)
|
||||
- `Publishers.Collect` (#76)
|
||||
- `Publishers.Comparison` (#76)
|
||||
- `Publishers.Drop` (#70, thank you @5sw!)
|
||||
- `Publishers.Scan` (#83, thank you @epatey!)
|
||||
- `Publishers.TryScan` (#83, thank you @epatey!)
|
||||
|
||||
### Bugfixes
|
||||
- `Publishers.Print` doesn't print a redundant whitespace anymore.
|
||||
|
||||
### Known issues
|
||||
- `@Published` property wrapper doesn't work yet
|
||||
|
||||
# 0.4.0 (8 Oct 2019)
|
||||
|
||||
This release is compatible with Xcode 11.1.
|
||||
|
||||
### Thread safety
|
||||
- `SubjectSubscriber` (which is used when you subscribe a subject to a publisher) has been audited for thread-safety
|
||||
- `Publishers.Multicast` has been audited for thread safety (#63)
|
||||
- `Publishers.TryMap` has been audited for thread safety
|
||||
- `Just` has been audited for thread safety
|
||||
- `Optional.Publisher` has been audited for thread safety
|
||||
- `Publishers.Sequence` has been audited for thread safety
|
||||
- `Publishers.ReplaceError` has been audited for thread safety
|
||||
- `Subscribers.Assign` has been audited for thread safety
|
||||
- `Subscribers.Sink` has been audited for thread safety
|
||||
|
||||
### Bugfixes
|
||||
- The semantics of `Publishers.Print`, `Publishers.TryMap` have been fixed
|
||||
- Fix `iterator.next()` being called twice in `Publishers.Sequence` (#62)
|
||||
- The default initializer of `CombineIdentifier` (the one that takes no arguments) is now much faster (#66, #69)
|
||||
- When `Publishers.Sequence` subscription is cancelled while it emits values, the cancellation is respected (#73, thanks @5sw!)
|
||||
|
||||
### Additions
|
||||
- `DispatchQueueScheduler` (#46)
|
||||
- `Equatable` conformances for `First`, `ReplaceError`
|
||||
- Added `eraseToAnyPublisher()` method (#59, thanks @evyasafhouzz for reporting!)
|
||||
- `Publishers.MakeConnectable` (#61)
|
||||
- `Publishers.Autoconnect` (#60)
|
||||
- `Publishers.Share` (#60)
|
||||
|
||||
### Known issues
|
||||
- `@Published` property wrapper doesn't work yet
|
||||
|
||||
# 0.3.0 (13 Sep 2019)
|
||||
|
||||
Among other things this release is compatible with Xcode 11.0 GM seed.
|
||||
|
||||
### Bugfixes
|
||||
- Store newly send value in internal variable inside CurrentValueObject (#39, thanks @FranzBusch!)
|
||||
|
||||
### Additions
|
||||
- `Filter`/`TryFilter` (#22, thanks @spadafiva!)
|
||||
- `First`/`FirstWhere`/`TryFirstWhere` (#22, thanks again @spadafiva!)
|
||||
- `CompactMap`/`TryCompacrMap` (#32)
|
||||
- `IgnoreOutput` (#44, thanks @epatey!)
|
||||
- `ReplaceError` (#50, thanks @vladiulianbogdan!)
|
||||
- `FlatMap` (#45, thanks again @epatey!)
|
||||
|
||||
### Known issues
|
||||
- `@Published` property wrapper doesn't work yet
|
||||
|
||||
# 0.2.0 (31 Jul 2019)
|
||||
|
||||
Updated for the newest Xcode 11.0 beta 5
|
||||
|
||||
# 0.1.0 (4 Jul 2019)
|
||||
|
||||
The first pre-pre-pre-alpha release is here!
|
||||
|
||||
Lots of stuff still unimplemented.
|
||||
|
||||
For now we have:
|
||||
|
||||
- `Just`
|
||||
- `Publishers.Decode`
|
||||
- `Publishers.DropWhile`
|
||||
- `Publishers.Empty`
|
||||
- `Publishers.Encode`
|
||||
- `Publishers.Fail`
|
||||
- `Publishers.Map`
|
||||
- `Publishers.Multicast`
|
||||
- `Publishers.Once`
|
||||
- `Publishers.Optional`
|
||||
- `Publishers.Print`
|
||||
- `Publishers.Sequence`
|
||||
- `Subscribers.Assign`
|
||||
- `Subscribers.Completion`
|
||||
- `Subscribers.Demand`
|
||||
- `Subscribers.Sink`
|
||||
- `AnyCancellable`
|
||||
- `AnyPublisher`
|
||||
- `AnySubject`
|
||||
- `AnySubscriber`
|
||||
- `Cancellable`
|
||||
- `CombineIdentifier`
|
||||
- `ConnectablePublisher`
|
||||
- `CurrentValueSubject`
|
||||
- `CustomCombineIdentifierConvertible`
|
||||
- `ImmediateScheduler`
|
||||
- `PassthroughSubject`
|
||||
- `Publisher`
|
||||
- `Result`
|
||||
- `Scheduler`
|
||||
- `Subject`
|
||||
- `Subscriber`
|
||||
- `Subscription`
|
||||
+3
-3
@@ -66,10 +66,10 @@ do {
|
||||
}
|
||||
}
|
||||
|
||||
SwiftLint.lint(inline: true,
|
||||
SwiftLint.lint(.all(directory: nil),
|
||||
inline: true,
|
||||
configFile: ".swiftlint.yml",
|
||||
strict: true,
|
||||
lintAllFiles: true)
|
||||
strict: true)
|
||||
|
||||
if danger.warnings.isEmpty, danger.fails.isEmpty {
|
||||
markdown("LGTM")
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombine"
|
||||
spec.version = "0.10.2"
|
||||
spec.version = "0.12.0"
|
||||
spec.summary = "Open source implementation of Apple's Combine framework for processing values over time."
|
||||
|
||||
spec.description = <<-DESC
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineDispatch"
|
||||
spec.version = "0.10.2"
|
||||
spec.version = "0.12.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.10.1'
|
||||
spec.dependency "OpenCombine", '>= 0.10.2'
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineFoundation"
|
||||
spec.version = "0.10.2"
|
||||
spec.version = "0.12.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.10.1'
|
||||
spec.dependency "OpenCombine", '>= 0.10.2'
|
||||
end
|
||||
|
||||
+73
-9
@@ -1,25 +1,89 @@
|
||||
// swift-tools-version:5.0
|
||||
// swift-tools-version:5.3
|
||||
|
||||
import PackageDescription
|
||||
|
||||
// This list should be updated whenever SwiftPM adds support for a new platform.
|
||||
// See: https://bugs.swift.org/browse/SR-13814
|
||||
let supportedPlatforms: [Platform] = [
|
||||
.macOS,
|
||||
.iOS,
|
||||
.watchOS,
|
||||
.tvOS,
|
||||
.linux,
|
||||
.android,
|
||||
// Disable Windows because of https://bugs.swift.org/browse/SR-13817
|
||||
// .windows,
|
||||
.wasi,
|
||||
]
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombine",
|
||||
dependencies: [
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
],
|
||||
exclude: [
|
||||
"Publishers/Publishers.Encode.swift.gyb",
|
||||
"Publishers/Publishers.MapKeyPath.swift.gyb",
|
||||
"Publishers/Publishers.Catch.swift.gyb"
|
||||
],
|
||||
swiftSettings: [.define("WASI", .when(platforms: [.wasi]))]
|
||||
),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
.target(
|
||||
name: "OpenCombineFoundation",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "OpenCombineTests",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-enable-testing"]),
|
||||
.define("WASI", .when(platforms: [.wasi]))
|
||||
]
|
||||
)
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension Array where Element == Platform {
|
||||
func except(_ exceptions: [Platform]) -> [Platform] {
|
||||
// See: https://bugs.swift.org/browse/SR-13813
|
||||
let exceptionsDescriptions = exceptions.map(String.init(describing:))
|
||||
return filter { platform in
|
||||
!exceptionsDescriptions.contains(String(describing: platform))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version:5.0
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation",
|
||||
]
|
||||
),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version:5.1
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation",
|
||||
]
|
||||
),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version:5.2
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation",
|
||||
]
|
||||
),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
@@ -2,38 +2,46 @@
|
||||
[](https://circleci.com/gh/OpenCombine/OpenCombine)
|
||||
[](https://codecov.io/gh/OpenCombine/OpenCombine)
|
||||

|
||||

|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/slack-OpenCombine-yellow.svg?logo=slack">](https://join.slack.com/t/opencombine/shared_invite/enQtNzE2MjE5NzkxODI0LTYxMjkzNDUxZWViZWI1Njc2YjBhODgxNjRjOTdkZTcxOGU2ZjJjZjYxMGI3NWZkN2RkNGFmZTUzNmU3MGE2ZWM)
|
||||
|
||||
Open-source implementation of Apple's [Combine](https://developer.apple.com/documentation/combine) framework for processing values over time.
|
||||
|
||||
The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux and Windows.
|
||||
|
||||
The project is in early development.
|
||||
The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux and WebAssembly.
|
||||
|
||||
### 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 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`. The same applies to Foundation: if you want to use, for instance, `NotificationCenter` or `URLSession` publishers, you'll need to also import `OpenCombineFoundation`.
|
||||
|
||||
If you develop code for multiple platforms, you may find it more convenient to import the
|
||||
`OpenCombineShim` module instead. It conditionally re-exports Combine on Apple platforms (if
|
||||
available), and all OpenCombine modules on other platforms. You can import `OpenCombineShim` only
|
||||
when using SwiftPM. It is not currently available for CocoaPods.
|
||||
|
||||
##### Swift Package Manager
|
||||
###### Swift Package
|
||||
To add `OpenCombine` to your [SPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file.
|
||||
To add `OpenCombine` to your [SwiftPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file. `OpenCombineDispatch` and `OpenCombineFoundation` products are currently not supported on WebAssembly. If your project targets WebAssembly exclusively, you should omit them from the list of your dependencies. If it targets multiple platforms including WebAssembly, depend on them only on non-WebAssembly platforms with [conditional target dependencies](https://github.com/apple/swift-evolution/blob/main/proposals/0273-swiftpm-conditional-target-dependencies.md).
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.10.2")
|
||||
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.12.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"])
|
||||
.target(
|
||||
name: "MyAwesomePackage",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.product(name: "OpenCombineFoundation", package: "OpenCombine"),
|
||||
.product(name: "OpenCombineDispatch", package: "OpenCombine")
|
||||
]
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
###### Xcode
|
||||
`OpenCombine` can also be added as a SPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
|
||||
`OpenCombine` can also be added as a SwiftPM 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:
|
||||
|
||||
@@ -46,9 +54,9 @@ 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.2'
|
||||
pod 'OpenCombineDispatch', '~> 0.10.2'
|
||||
pod 'OpenCombineFoundation', '~> 0.10.2'
|
||||
pod 'OpenCombine', '~> 0.12.0'
|
||||
pod 'OpenCombineDispatch', '~> 0.12.0'
|
||||
pod 'OpenCombineFoundation', '~> 0.12.0'
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
+34
-369
@@ -2,175 +2,15 @@
|
||||
// Please remove the corresponding piece from this file if you implement something,
|
||||
// and complement this file as features are added in Apple's Combine
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that receives and combines the latest elements from two publishers.
|
||||
public struct CombineLatest<A, B> : Publisher where A : Publisher, B : Publisher, A.Failure == B.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (A.Output, B.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public init(_ a: A, _ b: B)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, B.Failure == S.Failure, S.Input == (A.Output, B.Output)
|
||||
}
|
||||
|
||||
/// A publisher that receives and combines the latest elements from three publishers.
|
||||
public struct CombineLatest3<A, B, C> : Publisher where A : Publisher, B : Publisher, C : Publisher, A.Failure == B.Failure, B.Failure == C.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (A.Output, B.Output, C.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, S.Input == (A.Output, B.Output, C.Output)
|
||||
}
|
||||
|
||||
/// A publisher that receives and combines the latest elements from four publishers.
|
||||
public struct CombineLatest4<A, B, C, D> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, A.Failure == B.Failure, B.Failure == C.Failure, C.Failure == D.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (A.Output, B.Output, C.Output, D.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, S.Input == (A.Output, B.Output, C.Output, D.Output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Subscribes to an additional publisher and publishes a tuple upon receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers. However, it still obeys the demand-fulfilling rule of only sending the request amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream publishers. It implements this by using a buffer size of 1 for each upstream, and holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also fails.
|
||||
/// - Parameters:
|
||||
/// - other: Another publisher to combine with this one.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another publisher.
|
||||
public func combineLatest<P>(_ other: P) -> Publishers.CombineLatest<Self, P> where P : Publisher, Self.Failure == P.Failure
|
||||
|
||||
/// Subscribes to an additional publisher and invokes a closure upon receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers. However, it still obeys the demand-fulfilling rule of only sending the request amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream publishers. It implements this by using a buffer size of 1 for each upstream, and holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also fails.
|
||||
/// - Parameters:
|
||||
/// - other: Another publisher to combine with this one.
|
||||
/// - transform: A closure that receives the most recent value from each publisher and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another publisher.
|
||||
public func combineLatest<P, T>(_ other: P, _ transform: @escaping (Self.Output, P.Output) -> T) -> Publishers.Map<Publishers.CombineLatest<Self, P>, T> where P : Publisher, Self.Failure == P.Failure
|
||||
|
||||
/// Subscribes to two additional publishers and publishes a tuple upon receiving output from any of the publishers.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers. However, it still obeys the demand-fulfilling rule of only sending the request amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream publishers. It implements this by using a buffer size of 1 for each upstream, and holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finish. If an upstream publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also fails.
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - Returns: A publisher that receives and combines elements from this publisher and two other publishers.
|
||||
public func combineLatest<P, Q>(_ publisher1: P, _ publisher2: Q) -> Publishers.CombineLatest3<Self, P, Q> where P : Publisher, Q : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure
|
||||
|
||||
/// Subscribes to two additional publishers and invokes a closure upon receiving output from any of the publishers.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers. However, it still obeys the demand-fulfilling rule of only sending the request amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream publishers. It implements this by using a buffer size of 1 for each upstream, and holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finish. If an upstream publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also fails.
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - transform: A closure that receives the most recent value from each publisher and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this publisher and two other publishers.
|
||||
public func combineLatest<P, Q, T>(_ publisher1: P, _ publisher2: Q, _ transform: @escaping (Self.Output, P.Output, Q.Output) -> T) -> Publishers.Map<Publishers.CombineLatest3<Self, P, Q>, T> where P : Publisher, Q : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure
|
||||
|
||||
/// Subscribes to three additional publishers and publishes a tuple upon receiving output from any of the publishers.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers. However, it still obeys the demand-fulfilling rule of only sending the request amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream publishers. It implements this by using a buffer size of 1 for each upstream, and holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finish. If an upstream publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also fails.
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - publisher3: A fourth publisher to combine with this one.
|
||||
/// - Returns: A publisher that receives and combines elements from this publisher and three other publishers.
|
||||
public func combineLatest<P, Q, R>(_ publisher1: P, _ publisher2: Q, _ publisher3: R) -> Publishers.CombineLatest4<Self, P, Q, R> where P : Publisher, Q : Publisher, R : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure, Q.Failure == R.Failure
|
||||
|
||||
/// Subscribes to three additional publishers and invokes a closure upon receiving output from any of the publishers.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers. However, it still obeys the demand-fulfilling rule of only sending the request amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream publishers. It implements this by using a buffer size of 1 for each upstream, and holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finish. If an upstream publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also fails.
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - publisher3: A fourth publisher to combine with this one.
|
||||
/// - transform: A closure that receives the most recent value from each publisher and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this publisher and three other publishers.
|
||||
public func combineLatest<P, Q, R, T>(_ publisher1: P, _ publisher2: Q, _ publisher3: R, _ transform: @escaping (Self.Output, P.Output, Q.Output, R.Output) -> T) -> Publishers.Map<Publishers.CombineLatest4<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 strategy for collecting received elements.
|
||||
///
|
||||
/// - byTime: Collect and periodically publish items.
|
||||
/// - byTimeOrCount: Collect and publish items, either periodically or when a buffer reaches its maximum size.
|
||||
public enum TimeGroupingStrategy<Context> where Context : Scheduler {
|
||||
|
||||
/// A grouping that collects and periodically publishes items.
|
||||
case byTime(Context, Context.SchedulerTimeType.Stride)
|
||||
|
||||
/// A grouping that collects and publishes items periodically or when a buffer reaches a maximum size.
|
||||
case byTimeOrCount(Context, Context.SchedulerTimeType.Stride, Int)
|
||||
}
|
||||
|
||||
@@ -208,59 +48,43 @@ extension Publishers {
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Collects elements by a given strategy, and emits a single array of the collection.
|
||||
/// Collects elements by a given time-grouping strategy, and emits a single array of
|
||||
/// the collection.
|
||||
///
|
||||
/// Use `collect(_:options:)` to emit arrays of elements on a schedule specified by
|
||||
/// a `Scheduler` and `Stride` that you provide. At the end of each scheduled
|
||||
/// interval, the publisher sends an array that contains the items it collected.
|
||||
/// If the upstream publisher finishes before filling the buffer, the publisher sends
|
||||
/// an array that contains items it received. This may be fewer than the number of
|
||||
/// elements specified in the requested `Stride`.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
///
|
||||
/// The example above collects timestamps generated on a one-second `Timer` in groups
|
||||
/// (`Stride`) of five.
|
||||
///
|
||||
/// let sub = Timer.publish(every: 1, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .collect(.byTime(RunLoop.main, .seconds(5)))
|
||||
/// .sink { print("\($0)", terminator: "\n\n") }
|
||||
///
|
||||
/// // Prints: "[2020-01-24 00:54:46 +0000, 2020-01-24 00:54:47 +0000,
|
||||
/// // 2020-01-24 00:54:48 +0000, 2020-01-24 00:54:49 +0000,
|
||||
/// // 2020-01-24 00:54:50 +0000]"
|
||||
///
|
||||
/// > Note: When this publisher receives a request for `.max(n)` elements, it requests
|
||||
/// `.max(count * n)` from the upstream publisher.
|
||||
///
|
||||
/// If the upstream publisher finishes before filling the buffer, this publisher sends an array of all the items it has received. This may be fewer than `count` elements.
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error to the downstream receiver instead of sending its output.
|
||||
/// Note: When this publisher receives a request for `.max(n)` elements, it requests `.max(count * n)` from the upstream publisher.
|
||||
/// - Parameters:
|
||||
/// - strategy: The strategy with which to collect and publish elements.
|
||||
/// - options: `Scheduler` options to use for the strategy.
|
||||
/// - Returns: A publisher that collects elements by a given strategy, and emits a single array of the collection.
|
||||
/// - strategy: The timing group strategy used by the operator to collect and
|
||||
/// publish elements.
|
||||
/// - options: ``Scheduler`` options to use for the strategy.
|
||||
/// - Returns: A publisher that collects elements by a given strategy, and emits
|
||||
/// a single array of the collection.
|
||||
public func collect<S>(_ strategy: Publishers.TimeGroupingStrategy<S>, options: S.SchedulerOptions? = nil) -> Publishers.CollectByTime<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct PrefixUntilOutput<Upstream, Other> : Publisher where Upstream : Publisher, Other : 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
|
||||
|
||||
/// Another publisher, whose first output causes this publisher to finish.
|
||||
public let other: Other
|
||||
|
||||
public init(upstream: Upstream, other: Other)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements until another publisher emits an element.
|
||||
///
|
||||
/// After the second publisher publishes an element, the publisher returned by this method finishes.
|
||||
///
|
||||
/// - Parameter publisher: A second publisher.
|
||||
/// - Returns: A publisher that republishes elements until the second publisher publishes an element.
|
||||
public func prefix<P>(untilOutputFrom publisher: P) -> Publishers.PrefixUntilOutput<Self, P> where P : Publisher
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the merge function to two upstream publishers.
|
||||
@@ -651,115 +475,6 @@ extension Publisher {
|
||||
public func merge(with other: Self) -> Publishers.MergeMany<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that attempts to recreate its subscription to a failed upstream publisher.
|
||||
public struct Retry<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 retry attempts to perform.
|
||||
///
|
||||
/// If `nil`, this publisher attempts to reconnect with the upstream publisher an unlimited number of times.
|
||||
public let retries: Int?
|
||||
|
||||
/// Creates a publisher that attempts to recreate its subscription to a failed upstream publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher from which this publisher receives its elements.
|
||||
/// - retries: The maximum number of retry attempts to perform. If `nil`, this publisher attempts to reconnect with the upstream publisher an unlimited number of times.
|
||||
public init(upstream: Upstream, retries: Int?)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Attempts to recreate a failed subscription with the upstream publisher using a specified number of attempts to establish the connection.
|
||||
///
|
||||
/// After exceeding the specified number of retries, the publisher passes the failure to the downstream receiver.
|
||||
/// - Parameter retries: The number of times to attempt to recreate the subscription.
|
||||
/// - Returns: A publisher that attempts to recreate its subscription to a failed upstream publisher.
|
||||
public func retry(_ retries: Int) -> Publishers.Retry<Self>
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Attempts to recreate a failed subscription with the upstream publisher using a specified number of attempts to establish the connection.
|
||||
///
|
||||
/// After exceeding the specified number of retries, the publisher passes the failure to the downstream receiver.
|
||||
/// - Parameter retries: The number of times to attempt to recreate the subscription.
|
||||
/// - Returns: A publisher that attempts to recreate its subscription to a failed upstream publisher.
|
||||
public func retry(_ retries: Int) -> Publishers.Retry<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes either the most-recent or first element published by the upstream publisher in a specified time interval.
|
||||
public struct Throttle<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 interval in which to find and emit the most recent element.
|
||||
public let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler on which to publish elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// A Boolean value indicating whether to publish the most recent element.
|
||||
///
|
||||
/// If `false`, the publisher emits the first element received during the interval.
|
||||
public let latest: Bool
|
||||
|
||||
public init(upstream: Upstream, interval: Context.SchedulerTimeType.Stride, scheduler: Context, latest: Bool)
|
||||
|
||||
/// 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 either the most-recent or first element published by the upstream publisher in the specified time interval.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The interval at which to find and emit the most recent element, expressed in the time system of the scheduler.
|
||||
/// - scheduler: The scheduler on which to publish elements.
|
||||
/// - latest: A Boolean value that indicates whether to publish the most recent element. If `false`, the publisher emits the first element received during the interval.
|
||||
/// - Returns: A publisher that emits either the most-recent or first element received during the specified interval.
|
||||
public func throttle<S>(for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: Bool) -> Publishers.Throttle<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the zip function to two upstream publishers.
|
||||
@@ -923,43 +638,6 @@ 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.CombineLatest : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A combineLatest publisher to compare for equality.
|
||||
/// - rhs: Another combineLatest publisher to compare for equality.
|
||||
/// - Returns: `true` if the corresponding upstream publishers of each combineLatest publisher are equal, `false` otherwise.
|
||||
public static func == (lhs: Publishers.CombineLatest<A, B>, rhs: Publishers.CombineLatest<A, B>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.CombineLatest3 : Equatable where A : Equatable, B : Equatable, C : 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.CombineLatest3<A, B, C>, rhs: Publishers.CombineLatest3<A, B, C>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.CombineLatest4 : Equatable where A : Equatable, B : Equatable, C : Equatable, D : 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.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.
|
||||
@@ -1050,19 +728,6 @@ extension Publishers.MergeMany : Equatable where Upstream : Equatable {
|
||||
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.
|
||||
///
|
||||
/// 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.Retry<Upstream>, rhs: Publishers.Retry<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Zip : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
/// Subscriber implementations can use this type to provide a “cancellation token” that
|
||||
/// makes it possible for a caller to cancel a publisher, but not to use the
|
||||
/// `Subscription` object to request items.
|
||||
/// An AnyCancellable instance automatically calls `cancel()` when deinitialized.
|
||||
///
|
||||
/// An `AnyCancellable` instance automatically calls `cancel()` when deinitialized.
|
||||
public final class AnyCancellable: Cancellable, Hashable {
|
||||
|
||||
private var _cancel: (() -> Void)?
|
||||
@@ -46,18 +47,18 @@ public final class AnyCancellable: Cancellable, Hashable {
|
||||
|
||||
extension AnyCancellable {
|
||||
|
||||
/// Stores this AnyCancellable in the specified collection.
|
||||
/// Parameters:
|
||||
/// - collection: The collection to store this AnyCancellable.
|
||||
/// Stores this type-erasing cancellable instance in the specified collection.
|
||||
///
|
||||
/// - Parameter collection: The collection in which to store this `AnyCancellable`.
|
||||
public func store<Cancellables: RangeReplaceableCollection>(
|
||||
in collection: inout Cancellables
|
||||
) where Cancellables.Element == AnyCancellable {
|
||||
collection.append(self)
|
||||
}
|
||||
|
||||
/// Stores this AnyCancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - set: The set to store this AnyCancellable.
|
||||
/// Stores this type-erasing cancellable instance in the specified collection.
|
||||
///
|
||||
/// - Parameter collection: The collection in which to store this `AnyCancellable`.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
set.insert(self)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,43 @@ extension Publisher {
|
||||
|
||||
/// Wraps this publisher with a type eraser.
|
||||
///
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher` to
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher`` to
|
||||
/// the downstream subscriber, rather than this publisher’s actual type.
|
||||
/// This form of _type erasure_ preserves abstraction across API boundaries, such as
|
||||
/// different modules.
|
||||
/// When you expose your publishers as the `AnyPublisher` type, you can change
|
||||
/// the underlying implementation over time without affecting existing clients.
|
||||
///
|
||||
/// The following example shows two types that each have a `publisher` property.
|
||||
/// `TypeWithSubject` exposes this property as its actual type, `PassthroughSubject`,
|
||||
/// while `TypeWithErasedSubject` uses `eraseToAnyPublisher()` to expose it as
|
||||
/// an `AnyPublisher`. As seen in the output, a caller from another module can access
|
||||
/// `TypeWithSubject.publisher` as its native type. This means you can’t change your
|
||||
/// publisher to a different type without breaking the caller. By comparison,
|
||||
/// `TypeWithErasedSubject.publisher` appears to callers as an `AnyPublisher`, so you
|
||||
/// can change the underlying publisher type at will.
|
||||
///
|
||||
/// public class TypeWithSubject {
|
||||
/// public let publisher: some Publisher = PassthroughSubject<Int,Never>()
|
||||
/// }
|
||||
/// public class TypeWithErasedSubject {
|
||||
/// public let publisher: some Publisher = PassthroughSubject<Int,Never>()
|
||||
/// .eraseToAnyPublisher()
|
||||
/// }
|
||||
///
|
||||
/// // In another module:
|
||||
/// let nonErased = TypeWithSubject()
|
||||
/// if let subject = nonErased.publisher as? PassthroughSubject<Int,Never> {
|
||||
/// print("Successfully cast nonErased.publisher.")
|
||||
/// }
|
||||
/// let erased = TypeWithErasedSubject()
|
||||
/// if let subject = erased.publisher as? PassthroughSubject<Int,Never> {
|
||||
/// print("Successfully cast erased.publisher.")
|
||||
/// }
|
||||
///
|
||||
/// // Prints "Successfully cast nonErased.publisher."
|
||||
///
|
||||
/// - Returns: An ``AnyPublisher`` wrapping this publisher.
|
||||
@inlinable
|
||||
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
|
||||
return .init(self)
|
||||
@@ -20,7 +55,13 @@ extension Publisher {
|
||||
/// A type-erasing publisher.
|
||||
///
|
||||
/// Use `AnyPublisher` to wrap a publisher whose type has details you don’t want to expose
|
||||
/// to subscribers or other publishers.
|
||||
/// across API boundaries, such as different modules. Wrapping a `Subject` with
|
||||
/// `AnyPublisher` also prevents callers from accessing its `send(_:)` method. When you
|
||||
/// use type erasure this way, you can change the underlying publisher implementation over
|
||||
/// time without affecting existing clients.
|
||||
///
|
||||
/// You can use OpenCombine’s `eraseToAnyPublisher()` operator to wrap a publisher with
|
||||
/// `AnyPublisher`.
|
||||
public struct AnyPublisher<Output, Failure: Error>
|
||||
: CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
@@ -30,13 +71,17 @@ public struct AnyPublisher<Output, Failure: Error>
|
||||
|
||||
/// Creates a type-erasing publisher to wrap the provided publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher: A publisher to wrap with a type-eraser.
|
||||
/// - Parameter publisher: A publisher to wrap with a type-eraser.
|
||||
@inlinable
|
||||
public init<PublisherType: Publisher>(_ publisher: PublisherType)
|
||||
where Output == PublisherType.Output, Failure == PublisherType.Failure
|
||||
{
|
||||
box = PublisherBox(base: publisher)
|
||||
// If this has already been boxed, avoid boxing again
|
||||
if let erased = publisher as? AnyPublisher<Output, Failure> {
|
||||
box = erased.box
|
||||
} else {
|
||||
box = PublisherBox(base: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
@@ -61,7 +106,7 @@ extension AnyPublisher: Publisher {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
box.subscribe(subscriber)
|
||||
box.receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +143,6 @@ internal final class PublisherBox<PublisherType: Publisher>
|
||||
override internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
base.subscribe(subscriber)
|
||||
base.receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
/// A type-erasing subscriber.
|
||||
///
|
||||
/// Use an `AnySubscriber` to wrap an existing subscriber whose details you don’t want
|
||||
/// to expose. You can also use `AnySubscriber` to create a custom subscriber by providing
|
||||
/// closures for `Subscriber`’s methods, rather than implementing `Subscriber` directly.
|
||||
/// Use an `AnySubscriber` to wrap an existing subscriber whose details you don’t want to
|
||||
/// expose. You can also use `AnySubscriber` to create a custom subscriber by providing
|
||||
/// closures for the methods defined in `Subscriber`, rather than implementing
|
||||
/// `Subscriber` directly.
|
||||
public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
@@ -44,6 +45,11 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Input == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
if let erased = subscriber as? AnySubscriber<Input, Failure> {
|
||||
self = erased
|
||||
return
|
||||
}
|
||||
|
||||
combineIdentifier = subscriber.combineIdentifier
|
||||
|
||||
box = AnySubscriberBox(subscriber)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol indicating that an activity or action may be canceled.
|
||||
/// A protocol indicating that an activity or action supports cancellation.
|
||||
///
|
||||
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
|
||||
/// as timers, network access, or disk I/O.
|
||||
@@ -17,18 +17,18 @@ public protocol Cancellable {
|
||||
|
||||
extension Cancellable {
|
||||
|
||||
/// Stores this Cancellable in the specified collection.
|
||||
/// Parameters:
|
||||
/// - collection: The collection to store this Cancellable.
|
||||
/// Stores this cancellable instance in the specified collection.
|
||||
///
|
||||
/// - Parameter collection: The collection in which to store this `Cancellable`.
|
||||
public func store<Cancellables: RangeReplaceableCollection>(
|
||||
in collection: inout Cancellables
|
||||
) where Cancellables.Element == AnyCancellable {
|
||||
AnyCancellable(self).store(in: &collection)
|
||||
}
|
||||
|
||||
/// Stores this Cancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - set: The set to store this Cancellable.
|
||||
/// Stores this cancellable instance in the specified set.
|
||||
///
|
||||
/// - Parameter set: The set in which to store this `Cancellable`.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
AnyCancellable(self).store(in: &set)
|
||||
}
|
||||
|
||||
@@ -5,17 +5,25 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
/// A type that defines methods for decoding.
|
||||
public protocol TopLevelDecoder {
|
||||
|
||||
/// The type this decoder accepts.
|
||||
associatedtype Input
|
||||
|
||||
/// Decodes an instance of the indicated type.
|
||||
func decode<DecodablyType: Decodable>(_ type: DecodablyType.Type,
|
||||
from: Input) throws -> DecodablyType
|
||||
}
|
||||
|
||||
/// A type that defines methods for encoding.
|
||||
public protocol TopLevelEncoder {
|
||||
|
||||
/// The type this encoder produces.
|
||||
associatedtype Output
|
||||
|
||||
/// Encodes an instance of the indicated type.
|
||||
///
|
||||
/// - Parameter value: The instance to encode.
|
||||
func encode<EncodableType: Encodable>(_ value: EncodableType) throws -> Output
|
||||
}
|
||||
|
||||
@@ -9,19 +9,37 @@
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
#if WASI
|
||||
private var __identifier: UInt64 = 0
|
||||
|
||||
internal func __nextCombineIdentifier() -> UInt64 {
|
||||
defer { __identifier += 1 }
|
||||
return __identifier
|
||||
}
|
||||
#endif // WASI
|
||||
|
||||
/// A unique identifier for identifying publisher streams.
|
||||
///
|
||||
/// To conform to `CustomCombineIdentifierConvertible` in a
|
||||
/// `Subscription` or `Subject` that you implement as a structure, create an instance of
|
||||
/// `CombineIdentifier` as follows:
|
||||
///
|
||||
/// let combineIdentifier = CombineIdentifier()
|
||||
public struct CombineIdentifier: Hashable, CustomStringConvertible {
|
||||
|
||||
private let value: UInt64
|
||||
private let rawValue: UInt64
|
||||
|
||||
/// Creates a unique Combine identifier.
|
||||
public init() {
|
||||
value = __nextCombineIdentifier()
|
||||
rawValue = __nextCombineIdentifier()
|
||||
}
|
||||
|
||||
/// Creates a Combine identifier, using the bit pattern of the provided object.
|
||||
public init(_ obj: AnyObject) {
|
||||
value = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
|
||||
rawValue = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "0x\(String(value, radix: 16))"
|
||||
return "0x\(String(rawValue, radix: 16))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
/// A publisher that provides an explicit means of connecting and canceling publication.
|
||||
///
|
||||
/// Use a `ConnectablePublisher` when you need to perform additional configuration or
|
||||
/// setup prior to producing any elements.
|
||||
///
|
||||
/// This publisher doesn’t produce any elements until you call its `connect()` method.
|
||||
///
|
||||
/// Use `makeConnectable()` to create a `ConnectablePublisher` from any publisher whose
|
||||
/// failure type is `Never`.
|
||||
public protocol ConnectablePublisher: Publisher {
|
||||
|
||||
/// Connects to the publisher and returns a `Cancellable` instance with which
|
||||
/// to cancel publishing.
|
||||
/// Connects to the publisher, allowing it to produce elements, and returns
|
||||
/// an instance with which to cancel publishing.
|
||||
///
|
||||
/// - Returns: A `Cancellable` instance that can be used to cancel publishing.
|
||||
/// - Returns: A `Cancellable` instance that you use to cancel publishing.
|
||||
func connect() -> Cancellable
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
|
||||
/// A subject that wraps a single value and publishes a new element whenever the value
|
||||
/// changes.
|
||||
///
|
||||
/// Unlike `PassthroughSubject`, `CurrentValueSubject` maintains a buffer of the most
|
||||
/// recently published element.
|
||||
///
|
||||
/// Calling `send(_:)` on a `CurrentValueSubject` also updates the current value, making
|
||||
/// it equivalent to updating the `value` directly.
|
||||
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
@@ -5,8 +5,18 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol for uniquely identifying publisher streams.
|
||||
///
|
||||
/// If you create a custom `Subscription` or `Subscriber` type, implement this protocol
|
||||
/// so that development tools can uniquely identify publisher chains in your app.
|
||||
/// If your type is a class, OpenCombine provides an implementation of `combineIdentifier`
|
||||
/// for you.
|
||||
/// If your type is a structure, set up the identifier as follows:
|
||||
///
|
||||
/// let combineIdentifier = CombineIdentifier()
|
||||
public protocol CustomCombineIdentifierConvertible {
|
||||
|
||||
/// A unique identifier for identifying publisher streams.
|
||||
var combineIdentifier: CombineIdentifier { get }
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
// Created by Max Desiatov on 24/11/2019.
|
||||
//
|
||||
|
||||
/// A publisher that eventually produces one value and then finishes or fails.
|
||||
/// A publisher that eventually produces a single value and then finishes or fails.
|
||||
public final class Future<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// A type that represents a closure to invoke in the future, when an element or error
|
||||
/// is available.
|
||||
///
|
||||
/// The promise closure receives one parameter: a `Result` that contains either
|
||||
/// a single element published by a `Future`, or an error.
|
||||
public typealias Promise = (Result<Output, Failure>) -> Void
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
@@ -16,6 +21,11 @@ public final class Future<Output, Failure: Error>: Publisher {
|
||||
|
||||
private var result: Result<Output, Failure>?
|
||||
|
||||
/// Creates a publisher that invokes a promise closure when the publisher emits
|
||||
/// an element.
|
||||
///
|
||||
/// - Parameter attemptToFulfill: A `Promise` that the publisher invokes when
|
||||
/// the publisher emits an element or terminates with an error.
|
||||
public init(
|
||||
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// AbstractCombineLatest.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
internal class AbstractCombineLatest<Output, Failure, Downstream: Subscriber>
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
private let downstream: Downstream
|
||||
|
||||
// TODO: The size of these arrays always stays the same.
|
||||
// Maybe we can leverage ManagedBuffer/ManagedBufferPointer here
|
||||
// to avoid additional allocations.
|
||||
private var buffers: [Any?] // 0x78
|
||||
private var subscriptions: [Subscription?] // 0x80
|
||||
|
||||
private var demand = Subscribers.Demand.none // 0x88
|
||||
|
||||
private var recursion = false // 0x90
|
||||
|
||||
private var finished = false // 0x98
|
||||
|
||||
private var errored = false // 0xA0
|
||||
|
||||
private var cancelled = false // 0xA8
|
||||
|
||||
private let upstreamCount: Int // 0xB0
|
||||
|
||||
private var finishCount = 0 // 0xB8
|
||||
|
||||
private let lock = UnfairLock.allocate() // 0xC0
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate() // 0xC8
|
||||
|
||||
internal init(downstream: Downstream, upstreamCount: Int) {
|
||||
self.downstream = downstream
|
||||
self.buffers = Array(repeating: nil, count: upstreamCount)
|
||||
self.subscriptions = Array(repeating: nil, count: upstreamCount)
|
||||
self.upstreamCount = upstreamCount
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
// TODO: There should be more type-safe (and faster) way.
|
||||
// E. g. what if we store `buffers` in subclasses?
|
||||
internal func convert(values: [Any?]) -> Output {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
fileprivate final func receive(subscription: Subscription, index: Int) {
|
||||
lock.lock()
|
||||
guard !cancelled && subscriptions[index] == nil else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
subscriptions[index] = subscription
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
fileprivate final func receive(_ input: Any, index: Int) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if cancelled || finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
buffers[index] = input
|
||||
guard !recursion && demand > 0 && buffers.allSatisfy({ $0 != nil }) else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
demand -= 1
|
||||
recursion = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(convert(values: buffers))
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
recursion = false
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
fileprivate final func receive(completion: Subscribers.Completion<Failure>,
|
||||
index: Int) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finishCount += 1
|
||||
subscriptions[index] = nil
|
||||
if finishCount == upstreamCount {
|
||||
finished = true
|
||||
buffers = Array(repeating: nil, count: upstreamCount)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure:
|
||||
lock.lock()
|
||||
finished = true
|
||||
errored = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = Array(repeating: nil, count: upstreamCount)
|
||||
buffers = Array(repeating: nil, count: upstreamCount)
|
||||
lock.unlock()
|
||||
for (i, subscription) in subscriptions.enumerated() where i != index {
|
||||
subscription?.cancel()
|
||||
}
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AbstractCombineLatest: Subscription {
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero() // TODO: Test this
|
||||
lock.lock()
|
||||
guard !cancelled && !finished else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
for subscription in subscriptions {
|
||||
subscription?.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
cancelled = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = Array(repeating: nil, count: upstreamCount)
|
||||
buffers = Array(repeating: nil, count: upstreamCount)
|
||||
lock.unlock()
|
||||
for subscription in subscriptions {
|
||||
subscription?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AbstractCombineLatest: CustomStringConvertible {
|
||||
internal var description: String { return "CombineLatest" }
|
||||
}
|
||||
|
||||
extension AbstractCombineLatest: CustomReflectable {
|
||||
internal var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("upstreamSubscriptions", subscriptions),
|
||||
("demand", demand),
|
||||
("buffers", buffers)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
}
|
||||
|
||||
extension AbstractCombineLatest: CustomPlaygroundDisplayConvertible {
|
||||
internal final var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
extension AbstractCombineLatest {
|
||||
internal struct Side<Input>: Subscriber, CustomStringConvertible {
|
||||
private let index: Int
|
||||
private let combiner: AbstractCombineLatest
|
||||
|
||||
internal let combineIdentifier = CombineIdentifier()
|
||||
|
||||
internal init(index: Int, combiner: AbstractCombineLatest) {
|
||||
self.index = index
|
||||
self.combiner = combiner
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
combiner.receive(subscription: subscription, index: index)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return combiner.receive(input, index: index)
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
combiner.receive(completion: completion, index: index)
|
||||
}
|
||||
|
||||
internal var description: String { return "CombineLatest" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// DebugHook.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
internal final class DebugHook {
|
||||
|
||||
private struct Handler: Hashable {
|
||||
let handler: _Introspection
|
||||
|
||||
static func == (lhs: Handler, rhs: Handler) -> Bool {
|
||||
return lhs.handler === rhs.handler
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(handler))
|
||||
}
|
||||
}
|
||||
|
||||
internal static func getGlobalHook() -> DebugHook? {
|
||||
globalLock.lock()
|
||||
defer { globalLock.unlock() }
|
||||
return globalHook
|
||||
}
|
||||
|
||||
internal static func enable(_ handler: _Introspection) {
|
||||
let hook: DebugHook
|
||||
DebugHook.globalLock.lock()
|
||||
defer { DebugHook.globalLock.unlock() }
|
||||
if let _hook = DebugHook.globalHook {
|
||||
hook = _hook
|
||||
} else {
|
||||
hook = DebugHook()
|
||||
DebugHook.globalHook = hook
|
||||
}
|
||||
hook.lock.lock()
|
||||
defer { hook.lock.unlock() }
|
||||
hook.handlers.insert(Handler(handler: handler))
|
||||
}
|
||||
|
||||
internal static func disable(_ handler: _Introspection) {
|
||||
DebugHook.globalLock.lock()
|
||||
defer { DebugHook.globalLock.unlock() }
|
||||
guard let hook = DebugHook.globalHook else { return }
|
||||
hook.lock.lock()
|
||||
hook.handlers.remove(Handler(handler: handler))
|
||||
let noMoreHandlers = hook.handlers.isEmpty
|
||||
hook.lock.unlock()
|
||||
if noMoreHandlers {
|
||||
DebugHook.globalHook = nil
|
||||
}
|
||||
}
|
||||
|
||||
internal static func handlerIsEnabled(_ handler: _Introspection) -> Bool {
|
||||
DebugHook.globalLock.lock()
|
||||
defer { DebugHook.globalLock.unlock() }
|
||||
guard let hook = DebugHook.globalHook else { return false }
|
||||
hook.lock.lock()
|
||||
defer { hook.lock.unlock() }
|
||||
return hook.handlers.contains(Handler(handler: handler))
|
||||
}
|
||||
|
||||
private static var globalHook: DebugHook?
|
||||
|
||||
private static let globalLock = UnfairLock.allocate()
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var handlers = Set<Handler>()
|
||||
|
||||
internal var debugHandlers: [_Introspection] {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return handlers.map { $0.handler }
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
internal func willReceive<Upstream: Publisher, Downstream: Subscriber>(
|
||||
publisher: Upstream,
|
||||
subscriber: Downstream
|
||||
) where Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(publisher: publisher, subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Upstream: Publisher, Downstream: Subscriber>(
|
||||
publisher: Upstream,
|
||||
subscriber: Downstream
|
||||
) where Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(publisher: publisher, subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willReceive<Downstream: Subscriber>(subscriber: Downstream,
|
||||
subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(subscriber: subscriber, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Downstream: Subscriber>(subscriber: Downstream,
|
||||
subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(subscriber: subscriber, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willReceive<Downstream: Subscriber>(subscriber: Downstream,
|
||||
input: Downstream.Input) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(subscriber: subscriber, input: input)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Downstream: Subscriber>(
|
||||
subscriber: Downstream,
|
||||
input: Downstream.Input,
|
||||
resultingDemand: Subscribers.Demand
|
||||
) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(subscriber: subscriber,
|
||||
input: input,
|
||||
resultingDemand: resultingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willReceive<Downstream: Subscriber>(
|
||||
subscriber: Downstream,
|
||||
completion: Subscribers.Completion<Downstream.Failure>
|
||||
) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(subscriber: subscriber, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Downstream: Subscriber>(
|
||||
subscriber: Downstream,
|
||||
completion: Subscribers.Completion<Downstream.Failure>
|
||||
) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(subscriber: subscriber, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willRequest(subscription: Subscription, demand: Subscribers.Demand) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willRequest(subscription: subscription, demand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didRequest(subscription: Subscription, demand: Subscribers.Demand) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didRequest(subscription: subscription, demand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willCancel(subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willCancel(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didCancel(subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didCancel(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ internal class FilterProducer<Downstream: Subscriber,
|
||||
CustomReflectable
|
||||
where Downstream.Input == Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
// MARK: - State
|
||||
|
||||
private enum State {
|
||||
|
||||
@@ -9,5 +9,22 @@
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
#if WASI
|
||||
internal struct __UnfairLock { // swiftlint:disable:this type_name
|
||||
internal static func allocate() -> UnfairLock { return .init() }
|
||||
internal func lock() {}
|
||||
internal func unlock() {}
|
||||
internal func assertOwner() {}
|
||||
internal func deallocate() {}
|
||||
}
|
||||
|
||||
internal struct __UnfairRecursiveLock { // swiftlint:disable:this type_name
|
||||
internal static func allocate() -> UnfairRecursiveLock { return .init() }
|
||||
internal func lock() {}
|
||||
internal func unlock() {}
|
||||
internal func deallocate() {}
|
||||
}
|
||||
#endif // WASI
|
||||
|
||||
internal typealias UnfairLock = __UnfairLock
|
||||
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// PublishedSubject.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 29.10.2020.
|
||||
//
|
||||
|
||||
internal final class PublishedSubject<Output>: Subject {
|
||||
|
||||
internal typealias Failure = Never
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
|
||||
private var currentValue: Output
|
||||
|
||||
private var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
private var hasAnyDownstreamDemand = false
|
||||
|
||||
private var changePublisher: ObservableObjectPublisher?
|
||||
|
||||
internal var value: Output {
|
||||
get {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return currentValue
|
||||
}
|
||||
set {
|
||||
send(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
internal var objectWillChange: ObservableObjectPublisher? {
|
||||
get {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return changePublisher
|
||||
}
|
||||
set {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
changePublisher = newValue
|
||||
}
|
||||
}
|
||||
|
||||
internal init(_ value: Output) {
|
||||
self.currentValue = value
|
||||
}
|
||||
|
||||
deinit {
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
internal func send(subscription: Subscription) {
|
||||
lock.lock()
|
||||
upstreamSubscriptions.append(subscription)
|
||||
lock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
lock.lock()
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
}
|
||||
|
||||
internal func send(_ input: Output) {
|
||||
lock.lock()
|
||||
let downstreams = self.downstreams
|
||||
let changePublisher = self.changePublisher
|
||||
lock.unlock()
|
||||
changePublisher?.send()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.offer(input)
|
||||
}
|
||||
lock.lock()
|
||||
currentValue = input
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
internal func send(completion: Subscribers.Completion<Never>) {
|
||||
fatalError("unreachable")
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension PublishedSubject {
|
||||
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
|
||||
fileprivate var parent: PublishedSubject?
|
||||
|
||||
fileprivate var downstream: Downstream?
|
||||
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var deliveredCurrentValue = false
|
||||
|
||||
fileprivate init(parent: PublishedSubject,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.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
|
||||
}
|
||||
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 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()
|
||||
}
|
||||
|
||||
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 "PublishedSubject" }
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// PublishedSubscriber.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 29.10.2020.
|
||||
//
|
||||
|
||||
internal struct PublishedSubscriber<Value>: Subscriber {
|
||||
|
||||
internal typealias Input = Value
|
||||
|
||||
internal typealias Failure = Never
|
||||
|
||||
internal let combineIdentifier = CombineIdentifier()
|
||||
|
||||
private weak var subject: PublishedSubject<Value>?
|
||||
|
||||
internal init(_ subject: PublishedSubject<Value>) {
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
subject?.send(subscription: subscription)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Value) -> Subscribers.Demand {
|
||||
subject?.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Never>) {}
|
||||
}
|
||||
@@ -48,8 +48,6 @@ internal class ReduceProducer<Downstream: Subscriber,
|
||||
|
||||
private var upstreamCompleted = false
|
||||
|
||||
private var empty = true
|
||||
|
||||
internal init(downstream: Downstream, initial: Output?, reduce: Reducer) {
|
||||
self.downstream = downstream
|
||||
self.initial = initial
|
||||
@@ -100,7 +98,9 @@ internal class ReduceProducer<Downstream: Subscriber,
|
||||
return
|
||||
}
|
||||
upstreamCompleted = true
|
||||
self.completed = downstreamRequested || empty
|
||||
if downstreamRequested {
|
||||
self.completed = true
|
||||
}
|
||||
let completed = self.completed
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
@@ -157,7 +157,6 @@ extension ReduceProducer: Subscriber {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
empty = false
|
||||
lock.unlock()
|
||||
|
||||
// Combine doesn't hold the lock when calling `receive(newValue:)`.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// SubscriberTap.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
internal protocol SubscriberTapMarker {
|
||||
var inner: Any { mutating get }
|
||||
}
|
||||
|
||||
internal struct SubscriberTap<Subscriber: OpenCombine.Subscriber>
|
||||
: OpenCombine.Subscriber,
|
||||
CustomStringConvertible,
|
||||
SubscriberTapMarker
|
||||
{
|
||||
internal typealias Input = Subscriber.Input
|
||||
|
||||
internal typealias Failure = Subscriber.Failure
|
||||
|
||||
private let subscriber: Subscriber
|
||||
|
||||
internal lazy var inner: Any = AnySubscriber(self.subscriber)
|
||||
|
||||
internal init(subscriber: Subscriber) {
|
||||
self.subscriber = subscriber
|
||||
}
|
||||
|
||||
internal var combineIdentifier: CombineIdentifier {
|
||||
return subscriber.combineIdentifier
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
if let subscriptionTap = subscription as? SubscriptionTap {
|
||||
hook?.willReceive(subscriber: subscriber,
|
||||
subscription: subscriptionTap.subscription)
|
||||
subscriber.receive(subscription: subscriptionTap)
|
||||
hook?.didReceive(subscriber: subscriber,
|
||||
subscription: subscriptionTap.subscription)
|
||||
} else {
|
||||
hook?.willReceive(subscriber: subscriber, subscription: subscription)
|
||||
subscriber
|
||||
.receive(subscription: SubscriptionTap(subscription: subscription))
|
||||
hook?.didReceive(subscriber: subscriber, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willReceive(subscriber: subscriber, input: input)
|
||||
let newDemand = subscriber.receive(input)
|
||||
hook?.didReceive(subscriber: subscriber,
|
||||
input: input,
|
||||
resultingDemand: newDemand)
|
||||
return newDemand
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Subscriber.Failure>) {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willReceive(subscriber: subscriber, completion: completion)
|
||||
subscriber.receive(completion: completion)
|
||||
hook?.didReceive(subscriber: subscriber, completion: completion)
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
return String(describing: subscriber)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
internal enum SubscriptionStatus {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case pendingTerminal(Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
@@ -20,4 +21,13 @@ extension SubscriptionStatus {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal var subscription: Subscription? {
|
||||
switch self {
|
||||
case .awaitingSubscription, .terminal:
|
||||
return nil
|
||||
case let .subscribed(subscription), let .pendingTerminal(subscription):
|
||||
return subscription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// SubscriptionTap.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
internal struct SubscriptionTap: Subscription, CustomStringConvertible {
|
||||
|
||||
internal let subscription: Subscription
|
||||
|
||||
internal var combineIdentifier: CombineIdentifier {
|
||||
return subscription.combineIdentifier
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willRequest(subscription: subscription, demand: demand)
|
||||
subscription.request(demand)
|
||||
hook?.didRequest(subscription: subscription, demand: demand)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willCancel(subscription: subscription)
|
||||
subscription.cancel()
|
||||
hook?.didCancel(subscription: subscription)
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
return String(describing: subscription)
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
/// A scheduler for performing synchronous actions.
|
||||
///
|
||||
/// You can only use this scheduler for immediate actions. If you attempt to schedule
|
||||
/// actions after a specific date, this scheduler ignores the date and performs
|
||||
/// them immediately.
|
||||
/// actions after a specific date, this scheduler ignores the date and performs them
|
||||
/// immediately.
|
||||
public struct ImmediateScheduler: Scheduler {
|
||||
|
||||
/// The time type used by the immediate scheduler.
|
||||
@@ -41,29 +41,42 @@ public struct ImmediateScheduler: Scheduler {
|
||||
Codable,
|
||||
SchedulerTimeIntervalConvertible {
|
||||
|
||||
/// The type used when evaluating floating-point literals.
|
||||
public typealias FloatLiteralType = Double
|
||||
|
||||
/// The type used when evaluating integer literals.
|
||||
public typealias IntegerLiteralType = Int
|
||||
|
||||
/// The type used for expressing the stride’s magnitude.
|
||||
public typealias Magnitude = Int
|
||||
|
||||
/// The value of this time interval in seconds.
|
||||
public var magnitude: Int
|
||||
|
||||
/// Creates an immediate scheduler time interval from the given time interval.
|
||||
@inlinable
|
||||
public init(_ value: Int) {
|
||||
magnitude = value
|
||||
}
|
||||
|
||||
/// Creates an immediate scheduler time interval from an integer seconds
|
||||
/// value.
|
||||
@inlinable
|
||||
public init(integerLiteral value: Int) {
|
||||
self.init(value)
|
||||
}
|
||||
|
||||
/// Creates an immediate scheduler time interval from a floating-point seconds
|
||||
/// value.
|
||||
@inlinable
|
||||
public init(floatLiteral value: Double) {
|
||||
self.init(Int(value))
|
||||
}
|
||||
|
||||
/// Creates an immediate scheduler time interval from a binary integer type.
|
||||
///
|
||||
/// If `exactly` can’t convert to an `Int`, the resulting time interval is
|
||||
/// `nil`.
|
||||
@inlinable
|
||||
public init?<BinaryIntegerType: BinaryInteger>(
|
||||
exactly source: BinaryIntegerType
|
||||
@@ -119,6 +132,7 @@ public struct ImmediateScheduler: Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that defines options accepted by the immediate scheduler.
|
||||
public typealias SchedulerOptions = Never
|
||||
|
||||
/// The shared instance of the immediate scheduler.
|
||||
@@ -127,15 +141,21 @@ public struct ImmediateScheduler: Scheduler {
|
||||
/// the shared instance.
|
||||
public static let shared = ImmediateScheduler()
|
||||
|
||||
/// Performs the action at the next possible opportunity.
|
||||
@inlinable
|
||||
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
|
||||
action()
|
||||
}
|
||||
|
||||
/// The immediate scheduler’s definition of the current moment in time.
|
||||
public var now: SchedulerTimeType { return SchedulerTimeType() }
|
||||
|
||||
/// The minimum tolerance allowed by the immediate scheduler.
|
||||
public var minimumTolerance: SchedulerTimeType.Stride { return 0 }
|
||||
|
||||
/// Performs the action at some time after the specified date.
|
||||
///
|
||||
/// The immediate scheduler ignores `date` and performs the action immediately.
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
@@ -145,6 +165,8 @@ public struct ImmediateScheduler: Scheduler {
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
/// frequency, optionally taking into account tolerance if possible.
|
||||
///
|
||||
/// The immediate scheduler ignores `date` and performs the action immediately.
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
/// A type of object with a publisher that emits before the object has changed.
|
||||
///
|
||||
/// By default an `ObservableObject` will synthesize an `objectWillChange`
|
||||
/// publisher that emits before any of its `@Published` properties changes:
|
||||
/// By default an `ObservableObject` synthesizes an `objectWillChange` publisher that
|
||||
/// emits the changed value before any of its `@Published` properties changes.
|
||||
///
|
||||
/// class Contact : ObservableObject {
|
||||
/// @Published var name: String
|
||||
@@ -25,11 +25,13 @@
|
||||
/// }
|
||||
///
|
||||
/// let john = Contact(name: "John Appleseed", age: 24)
|
||||
/// john.objectWillChange.sink { _ in print("will change") }
|
||||
/// print(john.haveBirthday)
|
||||
/// // Prints "will change"
|
||||
/// cancellable = john.objectWillChange
|
||||
/// .sink { _ in
|
||||
/// print("\(john.age) will change")
|
||||
/// }
|
||||
/// print(john.haveBirthday())
|
||||
/// // Prints "24 will change"
|
||||
/// // Prints "25"
|
||||
///
|
||||
public protocol ObservableObject: AnyObject {
|
||||
|
||||
/// The type of publisher that emits before the object has changed.
|
||||
@@ -40,27 +42,59 @@ public protocol ObservableObject: AnyObject {
|
||||
var objectWillChange: ObjectWillChangePublisher { get }
|
||||
}
|
||||
|
||||
extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
// swiftlint:disable let_var_whitespace
|
||||
#if swift(>=5.1)
|
||||
/// A publisher that emits before the object has changed.
|
||||
@available(*, unavailable, message: """
|
||||
The default implementation of objectWillChange is not available yet. \
|
||||
It's being worked on in \
|
||||
https://github.com/broadwaylamb/OpenCombine/pull/97
|
||||
""")
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
fatalError("unimplemented")
|
||||
}
|
||||
#else
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
return ObservableObjectPublisher()
|
||||
}
|
||||
#endif
|
||||
// swiftlint:enable let_var_whitespace
|
||||
private protocol _ObservableObjectProperty {
|
||||
var objectWillChange: ObservableObjectPublisher? { get nonmutating set }
|
||||
}
|
||||
|
||||
/// The default publisher of an `ObservableObject`.
|
||||
#if swift(>=5.1)
|
||||
extension Published: _ObservableObjectProperty {}
|
||||
|
||||
extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
|
||||
/// A publisher that emits before the object has changed.
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
var installedPublisher: ObservableObjectPublisher?
|
||||
var reflection: Mirror? = Mirror(reflecting: self)
|
||||
while let aClass = reflection {
|
||||
for (_, property) in aClass.children {
|
||||
guard let property = property as? _ObservableObjectProperty else {
|
||||
// Visit other fields until we meet a @Published field
|
||||
continue
|
||||
}
|
||||
|
||||
// Now we know that the field is @Published.
|
||||
if let alreadyInstalledPublisher = property.objectWillChange {
|
||||
installedPublisher = alreadyInstalledPublisher
|
||||
// Don't visit other fields, as all @Published fields
|
||||
// already have a publisher installed.
|
||||
break
|
||||
}
|
||||
|
||||
// Okay, this field doesn't have a publisher installed.
|
||||
// This means that other fields don't have it either
|
||||
// (because we install it only once and fields can't be added at runtime).
|
||||
var lazilyCreatedPublisher: ObjectWillChangePublisher {
|
||||
if let publisher = installedPublisher {
|
||||
return publisher
|
||||
}
|
||||
let publisher = ObservableObjectPublisher()
|
||||
installedPublisher = publisher
|
||||
return publisher
|
||||
}
|
||||
|
||||
property.objectWillChange = lazilyCreatedPublisher
|
||||
|
||||
// Continue visiting other fields.
|
||||
}
|
||||
reflection = aClass.superclassMirror
|
||||
}
|
||||
return installedPublisher ?? ObservableObjectPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// A publisher that publishes changes from observable objects.
|
||||
public final class ObservableObjectPublisher: Publisher {
|
||||
|
||||
public typealias Output = Void
|
||||
@@ -74,6 +108,7 @@ public final class ObservableObjectPublisher: Publisher {
|
||||
// TODO: Combine needs this for some reason
|
||||
private var identifier: ObjectIdentifier?
|
||||
|
||||
/// Creates an observable object publisher instance.
|
||||
public init() {}
|
||||
|
||||
deinit {
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
/// A subject that passes along values and completion.
|
||||
/// A subject that broadcasts elements to downstream subscribers.
|
||||
///
|
||||
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
|
||||
/// specific values on-demand during tests.
|
||||
/// As a concrete implementation of `Subject`, the `PassthroughSubject` provides
|
||||
/// a convenient way to adapt existing imperative code to the Combine model.
|
||||
///
|
||||
/// Unlike `CurrentValueSubject`, a `PassthroughSubject` doesn’t have an initial value or
|
||||
/// a buffer of the most recently-published element.
|
||||
/// A `PassthroughSubject` drops values if there are no subscribers, or its current demand
|
||||
/// is zero.
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
@@ -6,29 +6,83 @@
|
||||
//
|
||||
|
||||
#if swift(>=5.1)
|
||||
/// Adds a `Publisher` to a property.
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Republishes elements received from a publisher, by assigning them to a property
|
||||
/// marked as a publisher.
|
||||
///
|
||||
/// Use this operator when you want to receive elements from a publisher and republish
|
||||
/// them through a property marked with the `@Published` attribute. The `assign(to:)`
|
||||
/// operator manages the life cycle of the subscription, canceling the subscription
|
||||
/// automatically when the `Published` instance deinitializes. Because of this,
|
||||
/// the `assign(to:)` operator doesn't return an `AnyCancellable` that you're
|
||||
/// responsible for like `assign(to:on:)` does.
|
||||
///
|
||||
/// The example below shows a model class that receives elements from an internal
|
||||
/// `Timer.TimerPublisher`, and assigns them to a `@Published` property called
|
||||
/// `lastUpdated`:
|
||||
///
|
||||
/// class MyModel: ObservableObject {
|
||||
/// @Published var lastUpdated: Date = Date()
|
||||
/// init() {
|
||||
/// Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
/// .autoconnect()
|
||||
/// .assign(to: $lastUpdated)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// If you instead implemented `MyModel` with `assign(to: lastUpdated, on: self)`,
|
||||
/// storing the returned `AnyCancellable` instance could cause a reference cycle,
|
||||
/// because the `Subscribers.Assign` subscriber would hold a strong reference
|
||||
/// to `self`. Using `assign(to:)` solves this problem.
|
||||
///
|
||||
/// - Parameter published: A property marked with the `@Published` attribute, which
|
||||
/// receives and republishes all elements received from the upstream publisher.
|
||||
public func assign(to published: inout Published<Output>.Publisher) {
|
||||
subscribe(PublishedSubscriber(published.subject))
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that publishes a property marked with an attribute.
|
||||
///
|
||||
/// Properties annotated with `@Published` contain both the stored value
|
||||
/// and a publisher which sends any new values after the property value
|
||||
/// has been sent. New subscribers will receive the current value
|
||||
/// of the property first.
|
||||
/// Note that the `@Published` property is class-constrained.
|
||||
/// Use it with properties of classes, not with non-class types like structures.
|
||||
/// Publishing a property with the `@Published` attribute creates a publisher of this
|
||||
/// type. You access the publisher with the `$` operator, as shown here:
|
||||
///
|
||||
/// class Weather {
|
||||
/// @Published var temperature: Double
|
||||
/// init(temperature: Double) {
|
||||
/// self.temperature = temperature
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let weather = Weather(temperature: 20)
|
||||
/// cancellable = weather.$temperature
|
||||
/// .sink() {
|
||||
/// print ("Temperature now: \($0)")
|
||||
/// }
|
||||
/// weather.temperature = 25
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Temperature now: 20.0
|
||||
/// // Temperature now: 25.0
|
||||
///
|
||||
/// When the property changes, publishing occurs in the property's `willSet` block,
|
||||
/// meaning subscribers receive the new value before it's actually set on the property.
|
||||
/// In the above example, the second time the sink executes its closure, it receives
|
||||
/// the parameter value `25`. However, if the closure evaluated `weather.temperature`,
|
||||
/// the value returned would be `20`.
|
||||
///
|
||||
/// > Important: The `@Published` attribute is class constrained. Use it with properties
|
||||
/// of classes, not with non-class types like structures.
|
||||
///
|
||||
/// ### See Also
|
||||
///
|
||||
/// - `Publisher.assign(to:)`
|
||||
@available(swift, introduced: 5.1)
|
||||
@propertyWrapper
|
||||
public struct Published<Value> {
|
||||
|
||||
@inlinable // trivially forwarding
|
||||
public init(initialValue: Value) {
|
||||
self.init(wrappedValue: initialValue)
|
||||
}
|
||||
|
||||
/// Initialize the storage of the `Published` property as well as the corresponding
|
||||
/// `Publisher`.
|
||||
public init(wrappedValue: Value) {
|
||||
value = wrappedValue
|
||||
}
|
||||
|
||||
/// A publisher for properties marked with the `@Published` attribute.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
@@ -36,38 +90,100 @@ public struct Published<Value> {
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
fileprivate let subject: PublishedSubject<Value>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Value, Downstream.Failure == Never
|
||||
{
|
||||
subject.subscribe(subscriber)
|
||||
}
|
||||
|
||||
fileprivate let subject: OpenCombine.CurrentValueSubject<Value, Never>
|
||||
|
||||
fileprivate init(_ output: Output) {
|
||||
subject = .init(output)
|
||||
}
|
||||
}
|
||||
|
||||
private var value: Value
|
||||
private enum Storage {
|
||||
case value(Value)
|
||||
case publisher(Publisher)
|
||||
}
|
||||
@propertyWrapper
|
||||
private final class Box {
|
||||
var wrappedValue: Storage
|
||||
|
||||
private var publisher: Publisher?
|
||||
|
||||
internal var objectWillChange: ObservableObjectPublisher?
|
||||
|
||||
/// The property that can be accessed with the `$` syntax and allows access to
|
||||
/// the `Publisher`
|
||||
public var projectedValue: Publisher {
|
||||
mutating get {
|
||||
if let publisher = publisher {
|
||||
return publisher
|
||||
}
|
||||
let publisher = Publisher(value)
|
||||
self.publisher = publisher
|
||||
return publisher
|
||||
init(wrappedValue: Storage) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
@Box private var storage: Storage
|
||||
|
||||
internal var objectWillChange: ObservableObjectPublisher? {
|
||||
get {
|
||||
switch storage {
|
||||
case .value:
|
||||
return nil
|
||||
case .publisher(let publisher):
|
||||
return publisher.subject.objectWillChange
|
||||
}
|
||||
}
|
||||
nonmutating set {
|
||||
getPublisher().subject.objectWillChange = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the published instance with an initial wrapped value.
|
||||
///
|
||||
/// Don't use this initializer directly. Instead, create a property with
|
||||
/// the `@Published` attribute, as shown here:
|
||||
///
|
||||
/// @Published var lastUpdated: Date = Date()
|
||||
///
|
||||
/// - Parameter wrappedValue: The publisher's initial value.
|
||||
public init(initialValue: Value) {
|
||||
self.init(wrappedValue: initialValue)
|
||||
}
|
||||
|
||||
/// Creates the published instance with an initial value.
|
||||
///
|
||||
/// Don't use this initializer directly. Instead, create a property with
|
||||
/// the `@Published` attribute, as shown here:
|
||||
///
|
||||
/// @Published var lastUpdated: Date = Date()
|
||||
///
|
||||
/// - Parameter initialValue: The publisher's initial value.
|
||||
public init(wrappedValue: Value) {
|
||||
_storage = Box(wrappedValue: .value(wrappedValue))
|
||||
}
|
||||
|
||||
/// The property for which this instance exposes a publisher.
|
||||
///
|
||||
/// The `projectedValue` is the property accessed with the `$` operator.
|
||||
public var projectedValue: Publisher {
|
||||
mutating get {
|
||||
return getPublisher()
|
||||
}
|
||||
set { // swiftlint:disable:this unused_setter_value
|
||||
switch storage {
|
||||
case .value(let value):
|
||||
storage = .publisher(Publisher(value))
|
||||
case .publisher:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This method can mutate `storage`
|
||||
internal func getPublisher() -> Publisher {
|
||||
switch storage {
|
||||
case .value(let value):
|
||||
let publisher = Publisher(value)
|
||||
storage = .publisher(publisher)
|
||||
return publisher
|
||||
case .publisher(let publisher):
|
||||
return publisher
|
||||
}
|
||||
}
|
||||
// swiftlint:disable let_var_whitespace
|
||||
@available(*, unavailable, message: """
|
||||
@Published is only available on properties of classes
|
||||
@@ -84,12 +200,20 @@ public struct Published<Value> {
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
|
||||
) -> Value {
|
||||
get {
|
||||
return object[keyPath: storageKeyPath].value
|
||||
switch object[keyPath: storageKeyPath].storage {
|
||||
case .value(let value):
|
||||
return value
|
||||
case .publisher(let publisher):
|
||||
return publisher.subject.value
|
||||
}
|
||||
}
|
||||
set {
|
||||
object[keyPath: storageKeyPath].objectWillChange?.send()
|
||||
object[keyPath: storageKeyPath].publisher?.subject.send(newValue)
|
||||
object[keyPath: storageKeyPath].value = newValue
|
||||
switch object[keyPath: storageKeyPath].storage {
|
||||
case .value:
|
||||
object[keyPath: storageKeyPath].storage = .publisher(Publisher(newValue))
|
||||
case .publisher(let publisher):
|
||||
publisher.subject.value = newValue
|
||||
}
|
||||
}
|
||||
// TODO: Benchmark and explore a possibility to use _modify
|
||||
}
|
||||
|
||||
@@ -7,18 +7,40 @@
|
||||
|
||||
/// Declares that a type can transmit a sequence of values over time.
|
||||
///
|
||||
/// There are four kinds of messages:
|
||||
/// subscription - A connection between `Publisher` and `Subscriber`.
|
||||
/// value - An element in the sequence.
|
||||
/// error - The sequence ended with an error (`.failure(e)`).
|
||||
/// complete - The sequence ended successfully (`.finished`).
|
||||
/// A publisher delivers elements to one or more `Subscriber` instances.
|
||||
/// The subscriber’s `Input` and `Failure` associated types must match the `Output` and
|
||||
/// `Failure` types declared by the publisher.
|
||||
/// The publisher implements the `receive(subscriber:)`method to accept a subscriber.
|
||||
///
|
||||
/// Both `.failure` and `.finished` are terminal messages.
|
||||
/// After this, the publisher can call the following methods on the subscriber:
|
||||
/// - `receive(subscription:)`: Acknowledges the subscribe request and returns
|
||||
/// a `Subscription` instance. The subscriber uses the subscription to demand elements
|
||||
/// from the publisher and can use it to cancel publishing.
|
||||
/// - `receive(_:)`: Delivers one element from the publisher to the subscriber.
|
||||
/// - `receive(completion:)`: Informs the subscriber that publishing has ended,
|
||||
/// either normally or with an error.
|
||||
///
|
||||
/// You can summarize these possibilities with a regular expression:
|
||||
/// value*(error|finished)?
|
||||
/// Every `Publisher` must adhere to this contract for downstream subscribers to function
|
||||
/// correctly.
|
||||
///
|
||||
/// Every `Publisher` must adhere to this contract.
|
||||
/// Extensions on `Publisher` define a wide variety of _operators_ that you compose to
|
||||
/// create sophisticated event-processing chains.
|
||||
/// Each operator returns a type that implements the `Publisher` protocol
|
||||
/// Most of these types exist as extensions on the `Publishers` enumeration.
|
||||
/// For example, the `map(_:)` operator returns an instance of `Publishers.Map`.
|
||||
///
|
||||
/// # Creating Your Own Publishers
|
||||
///
|
||||
/// Rather than implementing the `Publisher` protocol yourself, you can create your own
|
||||
/// publisher by using one of several types provided by the OpenCombine framework:
|
||||
///
|
||||
/// - Use a concrete subclass of `Subject`, such as `PassthroughSubject`, to publish
|
||||
/// values on-demand by calling its `send(_:)` method.
|
||||
/// - Use a `CurrentValueSubject` to publish whenever you update the subject’s underlying
|
||||
/// value.
|
||||
/// - Add the `@Published` annotation to a property of one of your own types. In doing so,
|
||||
/// the property gains a publisher that emits an event whenever the property’s value
|
||||
/// changes. See the `Published` type for an example of this approach.
|
||||
public protocol Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
@@ -29,13 +51,15 @@ public protocol Publisher {
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
associatedtype Failure: Error
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher`
|
||||
/// by `subscribe(_:)`
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
/// Always call this function instead of `receive(subscriber:)`.
|
||||
/// Adopters of `Publisher` must implement `receive(subscriber:)`. The implementation
|
||||
/// of `subscribe(_:)` provided by `Publisher` calls through to
|
||||
/// `receive(subscriber:)`.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to attach to this publisher. After
|
||||
/// attaching, the subscriber can start to receive values.
|
||||
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
}
|
||||
@@ -55,9 +79,27 @@ extension Publisher {
|
||||
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
{
|
||||
receive(subscriber: subscriber)
|
||||
if let hook = DebugHook.getGlobalHook() {
|
||||
if var marker = subscriber as? SubscriberTapMarker {
|
||||
let anySubscriber = marker.inner
|
||||
as! AnySubscriber<Subscriber.Input, Subscriber.Failure>
|
||||
hook.willReceive(publisher: self, subscriber: anySubscriber)
|
||||
receive(subscriber: subscriber)
|
||||
hook.didReceive(publisher: self, subscriber: anySubscriber)
|
||||
} else {
|
||||
let tap = SubscriberTap(subscriber: subscriber)
|
||||
hook.willReceive(publisher: self, subscriber: subscriber)
|
||||
receive(subscriber: tap)
|
||||
hook.didReceive(publisher: self, subscriber: subscriber)
|
||||
}
|
||||
} else {
|
||||
receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches the specified subject to this publisher.
|
||||
///
|
||||
/// - Parameter subject: The subject to attach to this publisher.
|
||||
public func subscribe<Subject: OpenCombine.Subject>(
|
||||
_ subject: Subject
|
||||
) -> AnyCancellable
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct Deferred<DeferredPublisher: Publisher>: Publisher {
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = DeferredPublisher.Failure
|
||||
|
||||
/// The closure to execute when it receives a subscription.
|
||||
/// The closure to execute when this deferred publisher receives a subscription.
|
||||
///
|
||||
/// The publisher returned by this closure immediately
|
||||
/// receives the incoming subscription.
|
||||
|
||||
@@ -24,6 +24,7 @@ public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
///
|
||||
/// Use this initializer to connect the empty publisher to subscribers or other
|
||||
/// publishers that have specific output and failure types.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - completeImmediately: A Boolean value that indicates whether the publisher
|
||||
/// should immediately finish.
|
||||
|
||||
@@ -20,6 +20,7 @@ public struct Fail<Output, Failure: Error>: Publisher {
|
||||
///
|
||||
/// Use this initializer to create a `Fail` publisher that can work with
|
||||
/// subscribers or publishers that expect a given output type.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failure: The failure to send when terminating the publisher.
|
||||
|
||||
+76
-23
@@ -15,27 +15,30 @@ 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
|
||||
/// }
|
||||
/// }
|
||||
/// Use `catch()` to replace an error from an upstream publisher with a new publisher.
|
||||
///
|
||||
/// In the example below, the `catch()` operator handles the `SimpleError` thrown by
|
||||
/// the upstream publisher by replacing the error with a `Just` publisher. This
|
||||
/// continues the stream by publishing a single value and completing normally.
|
||||
///
|
||||
/// struct SimpleError: Error {}
|
||||
/// let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryLast(where: {
|
||||
/// guard $0 != 0 else { throw SimpleError() }
|
||||
/// return true
|
||||
/// })
|
||||
/// .catch { error in
|
||||
/// Just(-1)
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
/// // Prints: -1
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// - SeeAlso: `replaceError`
|
||||
/// - 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
|
||||
@@ -49,13 +52,48 @@ extension Publisher {
|
||||
}
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another
|
||||
/// publisher or `throw`ing a new error.
|
||||
/// publisher or throwing 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.
|
||||
/// Use `tryCatch(_:)` to decide how to handle from an upstream publisher by either
|
||||
/// replacing the publisher with a new publisher, or throwing a new error.
|
||||
///
|
||||
/// In the example below, an array publisher emits values that a `tryMap(_:)` operator
|
||||
/// evaluates to ensure the values are greater than zero. If the values aren’t greater
|
||||
/// than zero, the operator throws an error to the downstream subscriber to let it
|
||||
/// know there was a problem. The subscriber, `tryCatch(_:)`, replaces the error with
|
||||
/// a new publisher using ``Just`` to publish a final value before the stream ends
|
||||
/// normally.
|
||||
///
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// var numbers = [5, 4, 3, 2, 1, -1, 7, 8, 9, 10]
|
||||
///
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMap { v in
|
||||
/// if v > 0 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
/// .tryCatch { error in
|
||||
/// Just(0) // Send a final value before completing normally.
|
||||
/// // Alternatively, throw a new error to terminate the stream.
|
||||
/// }
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0).") },
|
||||
/// receiveValue: { print ("Received \($0).") })
|
||||
/// // Received 5.
|
||||
/// // Received 4.
|
||||
/// // Received 3.
|
||||
/// // Received 2.
|
||||
/// // Received 1.
|
||||
/// // Received 0.
|
||||
/// // Completion: finished.
|
||||
///
|
||||
/// - Parameter handler: A throwing closure that accepts the upstream failure as
|
||||
/// input. This closure can either replace the upstream publisher with a new one,
|
||||
/// or throw a new error to the downstream subscriber.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
/// the failed publisher with another publisher, or an error.
|
||||
public func tryCatch<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) throws -> NewPublisher
|
||||
) -> Publishers.TryCatch<Self, NewPublisher>
|
||||
@@ -105,8 +143,12 @@ extension Publishers {
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher or optionally producing a new error.
|
||||
/// A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher or producing a new error.
|
||||
///
|
||||
/// Because this publisher’s handler can throw an error, `Publishers.TryCatch` defines
|
||||
/// its `Failure` type as `Error`. This is different from `Publishers.Catch`, which
|
||||
/// gets its failure type from the replacement publisher.
|
||||
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
@@ -114,10 +156,21 @@ extension Publishers {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and either returns
|
||||
/// a publisher to replace the upstream publisher or throws an error.
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher or by throwing an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and either
|
||||
/// returns a publisher to replace the upstream publisher. If this closure
|
||||
/// throws an error, the publisher terminates with the thrown error.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.CombineLatest.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
// swiftlint:disable large_tuple
|
||||
|
||||
// MARK: - CombineLatest methods on Publisher
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Subscribes to an additional publisher and publishes a tuple upon
|
||||
/// receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - other: Another publisher to combine with this one.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
public func combineLatest<P: Publisher>(
|
||||
_ other: P
|
||||
) -> Publishers.CombineLatest<Self, P>
|
||||
where Failure == P.Failure
|
||||
{
|
||||
return .init(self, other)
|
||||
}
|
||||
|
||||
/// Subscribes to an additional publisher and invokes a closure
|
||||
/// upon receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - other: Another publisher to combine with this one.
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
public func combineLatest<P: Publisher, Result>(
|
||||
_ other: P,
|
||||
_ transform: @escaping (Output, P.Output) -> Result
|
||||
) -> Publishers.Map<Publishers.CombineLatest<Self, P>, Result>
|
||||
where Failure == P.Failure
|
||||
{
|
||||
return Publishers.CombineLatest(self, other).map {
|
||||
transform($0, $1)
|
||||
}
|
||||
}
|
||||
/// Subscribes to two additional publishers and publishes a tuple upon
|
||||
/// receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
public func combineLatest<P: Publisher, Q: Publisher>(
|
||||
_ publisher1: P,
|
||||
_ publisher2: Q
|
||||
) -> Publishers.CombineLatest3<Self, P, Q>
|
||||
where Failure == P.Failure,
|
||||
P.Failure == Q.Failure
|
||||
{
|
||||
return .init(self, publisher1, publisher2)
|
||||
}
|
||||
|
||||
/// Subscribes to two additional publishers and invokes a closure
|
||||
/// upon receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
public func combineLatest<P: Publisher, Q: Publisher, Result>(
|
||||
_ publisher1: P,
|
||||
_ publisher2: Q,
|
||||
_ transform: @escaping (Output, P.Output, Q.Output) -> Result
|
||||
) -> Publishers.Map<Publishers.CombineLatest3<Self, P, Q>, Result>
|
||||
where Failure == P.Failure,
|
||||
P.Failure == Q.Failure
|
||||
{
|
||||
return Publishers.CombineLatest3(self, publisher1, publisher2).map {
|
||||
transform($0, $1, $2)
|
||||
}
|
||||
}
|
||||
/// Subscribes to three additional publishers and publishes a tuple upon
|
||||
/// receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - publisher3: A fourth publisher to combine with this one.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
public func combineLatest<P: Publisher, Q: Publisher, R: Publisher>(
|
||||
_ publisher1: P,
|
||||
_ publisher2: Q,
|
||||
_ publisher3: R
|
||||
) -> Publishers.CombineLatest4<Self, P, Q, R>
|
||||
where Failure == P.Failure,
|
||||
P.Failure == Q.Failure,
|
||||
Q.Failure == R.Failure
|
||||
{
|
||||
return .init(self, publisher1, publisher2, publisher3)
|
||||
}
|
||||
|
||||
/// Subscribes to three additional publishers and invokes a closure
|
||||
/// upon receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher to combine with this one.
|
||||
/// - publisher2: A third publisher to combine with this one.
|
||||
/// - publisher3: A fourth publisher to combine with this one.
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
public func combineLatest<P: Publisher, Q: Publisher, R: Publisher, Result>(
|
||||
_ publisher1: P,
|
||||
_ publisher2: Q,
|
||||
_ publisher3: R,
|
||||
_ transform: @escaping (Output, P.Output, Q.Output, R.Output) -> Result
|
||||
) -> Publishers.Map<Publishers.CombineLatest4<Self, P, Q, R>, Result>
|
||||
where Failure == P.Failure,
|
||||
P.Failure == Q.Failure,
|
||||
Q.Failure == R.Failure
|
||||
{
|
||||
return Publishers.CombineLatest4(self, publisher1, publisher2, publisher3).map {
|
||||
transform($0, $1, $2, $3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CombineLatest publishers
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that receives and combines the latest elements from two
|
||||
/// publishers.
|
||||
public struct CombineLatest<A: Publisher, B: Publisher>
|
||||
: Publisher
|
||||
where A.Failure == B.Failure
|
||||
{
|
||||
public typealias Output = (A.Output, B.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 Downstream.Failure == Failure,
|
||||
Downstream.Input == Output
|
||||
{
|
||||
typealias Inner = CombineLatest2Inner<A.Output,
|
||||
B.Output,
|
||||
Failure,
|
||||
Downstream>
|
||||
let inner = Inner(downstream: subscriber, upstreamCount: 2)
|
||||
a.subscribe(Inner.Side(index: 0, combiner: inner))
|
||||
b.subscribe(Inner.Side(index: 1, combiner: inner))
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that receives and combines the latest elements from three
|
||||
/// publishers.
|
||||
public struct CombineLatest3<A: Publisher, B: Publisher, C: Publisher>
|
||||
: Publisher
|
||||
where A.Failure == B.Failure,
|
||||
B.Failure == C.Failure
|
||||
{
|
||||
public typealias Output = (A.Output, B.Output, C.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 Downstream.Failure == Failure,
|
||||
Downstream.Input == Output
|
||||
{
|
||||
typealias Inner = CombineLatest3Inner<A.Output,
|
||||
B.Output,
|
||||
C.Output,
|
||||
Failure,
|
||||
Downstream>
|
||||
let inner = Inner(downstream: subscriber, upstreamCount: 3)
|
||||
a.subscribe(Inner.Side(index: 0, combiner: inner))
|
||||
b.subscribe(Inner.Side(index: 1, combiner: inner))
|
||||
c.subscribe(Inner.Side(index: 2, combiner: inner))
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that receives and combines the latest elements from four
|
||||
/// publishers.
|
||||
public struct CombineLatest4<A: Publisher, B: Publisher, C: Publisher, D: Publisher>
|
||||
: Publisher
|
||||
where A.Failure == B.Failure,
|
||||
B.Failure == C.Failure,
|
||||
C.Failure == D.Failure
|
||||
{
|
||||
public typealias Output = (A.Output, B.Output, C.Output, D.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 Downstream.Failure == Failure,
|
||||
Downstream.Input == Output
|
||||
{
|
||||
typealias Inner = CombineLatest4Inner<A.Output,
|
||||
B.Output,
|
||||
C.Output,
|
||||
D.Output,
|
||||
Failure,
|
||||
Downstream>
|
||||
let inner = Inner(downstream: subscriber, upstreamCount: 4)
|
||||
a.subscribe(Inner.Side(index: 0, combiner: inner))
|
||||
b.subscribe(Inner.Side(index: 1, combiner: inner))
|
||||
c.subscribe(Inner.Side(index: 2, combiner: inner))
|
||||
d.subscribe(Inner.Side(index: 3, combiner: inner))
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable conformances
|
||||
|
||||
extension Publishers.CombineLatest: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable {}
|
||||
|
||||
extension Publishers.CombineLatest3: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable {}
|
||||
|
||||
extension Publishers.CombineLatest4: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable {}
|
||||
|
||||
// MARK: - Inners
|
||||
|
||||
private final class CombineLatest2Inner<Input0,
|
||||
Input1,
|
||||
Failure,
|
||||
Downstream: Subscriber>
|
||||
: AbstractCombineLatest<(Input0, Input1), Failure, Downstream>
|
||||
where Downstream.Input == (Input0, Input1),
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
override func convert(values: [Any?]) -> (Input0, Input1) {
|
||||
return (values[0] as! Input0,
|
||||
values[1] as! Input1)
|
||||
}
|
||||
}
|
||||
|
||||
private final class CombineLatest3Inner<Input0,
|
||||
Input1,
|
||||
Input2,
|
||||
Failure,
|
||||
Downstream: Subscriber>
|
||||
: AbstractCombineLatest<(Input0, Input1, Input2), Failure, Downstream>
|
||||
where Downstream.Input == (Input0, Input1, Input2),
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
override func convert(values: [Any?]) -> (Input0, Input1, Input2) {
|
||||
return (values[0] as! Input0,
|
||||
values[1] as! Input1,
|
||||
values[2] as! Input2)
|
||||
}
|
||||
}
|
||||
|
||||
private final class CombineLatest4Inner<Input0,
|
||||
Input1,
|
||||
Input2,
|
||||
Input3,
|
||||
Failure,
|
||||
Downstream: Subscriber>
|
||||
: AbstractCombineLatest<(Input0, Input1, Input2, Input3), Failure, Downstream>
|
||||
where Downstream.Input == (Input0, Input1, Input2, Input3),
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
override func convert(values: [Any?]) -> (Input0, Input1, Input2, Input3) {
|
||||
return (values[0] as! Input0,
|
||||
values[1] as! Input1,
|
||||
values[2] as! Input2,
|
||||
values[3] as! Input3)
|
||||
}
|
||||
}
|
||||
+158
-20
@@ -13,16 +13,100 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Encodes the output from upstream using a specified `TopLevelEncoder`.
|
||||
/// For example, use `JSONEncoder`.
|
||||
/// Encodes the output from upstream using a specified encoder.
|
||||
///
|
||||
/// Use `encode(encoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to encode an `Encodable` struct into `Data` that could be used to
|
||||
/// make a JSON string (or written to disk as a binary plist in the case of property
|
||||
/// lists).
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes an `Article`.
|
||||
/// The `encode(encoder:)` operator encodes the properties of the `Article` struct
|
||||
/// into a new JSON string according to the `Codable` protocol adopted by `Article`.
|
||||
/// The operator publishes the resulting JSON string to the downstream subscriber.
|
||||
/// If the encoding operation fails, which can happen in the case of complex
|
||||
/// properties that can’t be directly transformed into JSON, the stream terminates
|
||||
/// and the error is passed to the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Article, Never>()
|
||||
/// let cancellable = dataProvider
|
||||
/// .encode(encoder: JSONEncoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)") },
|
||||
/// receiveValue: { data in
|
||||
/// guard let stringRepresentation =
|
||||
/// String(data: data, encoding: .utf8) else { return }
|
||||
/// print("""
|
||||
/// Data received \(data) string representation: \
|
||||
/// \(stringRepresentation)
|
||||
/// """)
|
||||
/// })
|
||||
///
|
||||
/// dataProvider.send(Article(title: "My First Article",
|
||||
/// author: "Gita Kumar",
|
||||
/// pubDate: Date()))
|
||||
///
|
||||
/// // Prints: "Data received 86 bytes string representation:
|
||||
/// // {"title":"My First Article","author":"Gita Kumar"
|
||||
/// // "pubDate":606211803.279603}"
|
||||
///
|
||||
/// - Parameter encoder: An encoder that implements the `TopLevelEncoder` protocol.
|
||||
/// - Returns: A publisher that encodes received elements using a specified encoder,
|
||||
/// and publishes the resulting data.
|
||||
public func encode<Coder: TopLevelEncoder>(
|
||||
encoder: Coder
|
||||
) -> Publishers.Encode<Self, Coder> {
|
||||
return .init(upstream: self, encoder: encoder)
|
||||
}
|
||||
|
||||
/// Decodes the output from upstream using a specified `TopLevelDecoder`.
|
||||
/// For example, use `JSONDecoder`.
|
||||
/// Decodes the output from the upstream using a specified decoder.
|
||||
///
|
||||
/// Use `decode(type:decoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to decode data received from a `URLSession.DataTaskPublisher` or
|
||||
/// other data source using the `Decodable` protocol.
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes a JSON string. The JSON decoder
|
||||
/// parses the string, converting its fields according to the `Decodable` protocol
|
||||
/// implemented by `Article`, and successfully populating a new `Article`.
|
||||
/// The `Publishers.Decode` publisher then publishes the `Article` to the downstream.
|
||||
/// If a decoding operation fails, which happens in the case of missing or malformed
|
||||
/// data in the source JSON string, the stream terminates and passes the error to
|
||||
/// the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Data, Never>()
|
||||
/// cancellable = dataProvider
|
||||
/// .decode(type: Article.self, decoder: JSONDecoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)")},
|
||||
/// receiveValue: { print ("value: \($0)") })
|
||||
///
|
||||
/// dataProvider.send(Data("""
|
||||
/// {\"pubDate\":1574273638.575666, \
|
||||
/// \"title\" : \"My First Article\", \
|
||||
/// \"author\" : \"Gita Kumar\" }
|
||||
/// """.utf8))
|
||||
///
|
||||
/// // Prints:
|
||||
/// // ".sink() data received Article(title: "My First Article",
|
||||
/// // author: "Gita Kumar",
|
||||
/// // pubDate: 2050-11-20 18:13:58 +0000)"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - type: The encoded data to decode into a struct that conforms to
|
||||
/// the `Decodable` protocol.
|
||||
/// - decoder: A decoder that implements the `TopLevelDecoder` protocol.
|
||||
/// - Returns: A publisher that decodes a given type using a specified decoder and
|
||||
/// publishes the result.
|
||||
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
@@ -88,9 +172,6 @@ extension Publishers.Encode {
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
@@ -99,6 +180,8 @@ extension Publishers.Encode {
|
||||
|
||||
private let encode: (Upstream.Output) throws -> Output
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
@@ -111,44 +194,72 @@ extension Publishers.Encode {
|
||||
self.encode = encode
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if finished { return .none }
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
do {
|
||||
return try downstream.receive(encode(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if finished { return }
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let subscription = self.subscription, !finished else { return }
|
||||
subscription.cancel()
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Encode" }
|
||||
@@ -175,9 +286,6 @@ extension Publishers.Decode {
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
@@ -186,6 +294,8 @@ extension Publishers.Decode {
|
||||
|
||||
private let decode: (Upstream.Output) throws -> Output
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
@@ -198,44 +308,72 @@ extension Publishers.Decode {
|
||||
self.decode = decode
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if finished { return .none }
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
do {
|
||||
return try downstream.receive(decode(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if finished { return }
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let subscription = self.subscription, !finished else { return }
|
||||
subscription.cancel()
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Decode" }
|
||||
|
||||
@@ -12,11 +12,29 @@
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher that publishes the values of a keyt path as a tuple.
|
||||
/// Publishes the value of the key path.
|
||||
///
|
||||
/// In the following example, the `map(_:)` operator uses the Swift
|
||||
/// key path syntax to access the `die` member
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// the value of this `Int`,
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
/// let die: Int
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(die: Int.random(in: 1...6)))
|
||||
/// .map(\.die)
|
||||
/// .sink {
|
||||
/// print ("Rolled: \($0)")
|
||||
/// }
|
||||
/// // Prints "Rolled: 6 (or some other random value).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath: The key path of a property on `Output`
|
||||
/// - keyPath: The key path of a property on `Output`.
|
||||
/// - Returns: A publisher that publishes the value of the key path.
|
||||
public func map<Result>(
|
||||
_ keyPath: KeyPath<Output, Result>
|
||||
@@ -26,12 +44,35 @@ extension Publisher {
|
||||
keyPath: keyPath
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of two key paths as a tuple.
|
||||
/// Publishes the values of two key paths as a tuple.
|
||||
///
|
||||
/// In the following example, the `map(_:_:)` operator uses the Swift
|
||||
/// key path syntax to access the `die1` and `die2` members
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// these two values (as an `(Int, Int)` tuple),
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
/// let die1: Int
|
||||
/// let die2: Int
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(die1: Int.random(in: 1...6),
|
||||
/// die2: Int.random(in: 1...6)))
|
||||
/// .map(\.die1, \.die2)
|
||||
/// .sink { values in
|
||||
/// print("""
|
||||
/// Rolled: \(values.0), \(values.1) \
|
||||
/// (total \(values.0 + values.1))
|
||||
/// """)
|
||||
/// }
|
||||
/// // Prints "Rolled: 5, 3 (total: 8)" (or other random values).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - keyPath0: The key path of a property on `Output`.
|
||||
/// - keyPath1: The key path of another property on `Output`.
|
||||
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
|
||||
public func map<Result0, Result1>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
@@ -43,13 +84,38 @@ extension Publisher {
|
||||
keyPath1: keyPath1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
/// Publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// In the following example, the `map(_:_:_:)` operator uses the Swift
|
||||
/// key path syntax to access the `die1`, `die2`, and `die3` members
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// these three values (as an `(Int, Int, Int)` tuple),
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
/// let die1: Int
|
||||
/// let die2: Int
|
||||
/// let die3: Int
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(die1: Int.random(in: 1...6),
|
||||
/// die2: Int.random(in: 1...6),
|
||||
/// die3: Int.random(in: 1...6)))
|
||||
/// .map(\.die1, \.die2, \.die3)
|
||||
/// .sink { values in
|
||||
/// print("""
|
||||
/// Rolled: \(values.0), \(values.1), \(values.2) \
|
||||
/// (total \(values.0 + values.1 + values.2))
|
||||
/// """)
|
||||
/// }
|
||||
/// // Prints "Rolled: 2, 4, 3 (total: 9)" (or other random values).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - keyPath2: The key path of a third property on `Output`
|
||||
/// - keyPath0: The key path of a property on `Output`.
|
||||
/// - keyPath1: The key path of a second property on `Output`.
|
||||
/// - keyPath2: The key path of a third property on `Output`.
|
||||
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
|
||||
public func map<Result0, Result1, Result2>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
/// A publisher that emits an output to each subscriber just once, and then finishes.
|
||||
///
|
||||
/// You can use a `Just` publisher to start a chain of publishers. A `Just` publisher
|
||||
/// is also useful when replacing a value with `Catch`.
|
||||
/// You can use a `Just` publisher to start a chain of publishers. A `Just` publisher is
|
||||
/// also useful when replacing a value with `Publishers.Catch`.
|
||||
///
|
||||
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
|
||||
/// In contrast with `Result.Publisher`, a `Just` publisher can’t fail with an error.
|
||||
/// And unlike `Optional.Publisher`, a `Just` publisher always produces a value.
|
||||
public struct Just<Output>: Publisher {
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
@@ -28,28 +28,45 @@ extension Optional {
|
||||
self.optional = optional
|
||||
}
|
||||
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
public var publisher: Publisher {
|
||||
return Publisher(optional)
|
||||
}
|
||||
|
||||
/// The type of a Combine publisher that publishes the value of a Swift optional
|
||||
/// instance to each subscriber exactly once, if the instance has any value at
|
||||
/// all.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
/// In contrast with the `Just` publisher, which always produces a single value,
|
||||
/// this publisher might not send any values and instead finish normally,
|
||||
/// if `output` is `nil`.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
/// The kind of value published by this publisher.
|
||||
///
|
||||
/// This publisher produces the type wrapped by the optional.
|
||||
public typealias Output = Wrapped
|
||||
|
||||
/// The kind of error this publisher might publish.
|
||||
///
|
||||
/// The optional publisher never produces errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
/// The output to deliver to each subscriber.
|
||||
public let output: Wrapped?
|
||||
|
||||
/// Creates a publisher to emit the optional value of a successful result,
|
||||
/// or fail with an error.
|
||||
/// Creates a publisher to emit the value of the optional, or to finish
|
||||
/// immediately if the optional doesn't have a value.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
/// - Parameter output: The result to deliver to each subscriber.
|
||||
public init(_ output: Output?) {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
/// Implements the Publisher protocol by accepting the subscriber and
|
||||
/// immediately publishing the optional’s value if it has one, or finishing
|
||||
/// normally if it doesn’t.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to add.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
@@ -64,13 +81,23 @@ extension Optional {
|
||||
}
|
||||
}
|
||||
|
||||
public var ocombine: OCombine {
|
||||
return .init(self)
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
/// The type of a Combine publisher that publishes the value of a Swift optional
|
||||
/// instance to each subscriber exactly once, if the instance has any value at
|
||||
/// all.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
/// In contrast with the `Just` publisher, which always produces a single value,
|
||||
/// this publisher might not send any values and instead finish normally,
|
||||
/// if `output` is `nil`.
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
|
||||
public var publisher: Publisher {
|
||||
return Publisher(self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,28 @@ extension Publisher {
|
||||
/// Publishes a single Boolean value that indicates whether all received elements pass
|
||||
/// a given predicate.
|
||||
///
|
||||
/// When this publisher receives an element, it runs the predicate against
|
||||
/// the element. If the predicate returns `false`, the publisher produces a `false`
|
||||
/// value and finishes. If the upstream publisher finishes normally, this publisher
|
||||
/// produces a `true` value and finishes.
|
||||
/// As a `reduce`-style operator, this publisher produces at most one value.
|
||||
/// Backpressure note: Upon receiving any request greater than zero, this publisher
|
||||
/// requests unlimited elements from the upstream publisher.
|
||||
/// Use the `allSatisfy(_:)` operator to determine if all elements in a stream satisfy
|
||||
/// a criteria you provide. When this publisher receives an element, it runs
|
||||
/// the predicate against the element. If the predicate returns `false`, the publisher
|
||||
/// produces a `false` value and finishes. If the upstream publisher finishes
|
||||
/// normally, this publisher produces a `true` value and finishes.
|
||||
///
|
||||
/// In the example below, the `allSatisfy(_:)` operator tests if each an integer array
|
||||
/// publisher’s elements fall into the `targetRange`:
|
||||
///
|
||||
/// let targetRange = (-1...100)
|
||||
/// let numbers = [-1, 0, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .allSatisfy { targetRange.contains($0) }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "true"
|
||||
///
|
||||
/// With operators similar to `reduce(_:_:)`, this publisher produces at most one
|
||||
/// value.
|
||||
///
|
||||
/// > Note: Upon receiving any request greater than zero, this publisher requests
|
||||
/// unlimited elements from the upstream publisher.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element.
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
@@ -31,21 +46,45 @@ extension Publisher {
|
||||
/// Publishes a single Boolean value that indicates whether all received elements pass
|
||||
/// a given error-throwing predicate.
|
||||
///
|
||||
/// When this publisher receives an element, it runs the predicate against
|
||||
/// the element. If the predicate returns `false`, the publisher produces a `false`
|
||||
/// value and finishes. If the upstream publisher finishes normally, this publisher
|
||||
/// produces a `true` value and finishes. If the predicate throws an error,
|
||||
/// the publisher fails, passing the error to its downstream.
|
||||
/// As a `reduce`-style operator, this publisher produces at most one value.
|
||||
/// Backpressure note: Upon receiving any request greater than zero, this publisher
|
||||
/// requests unlimited elements from the upstream publisher.
|
||||
/// Use the `tryAllSatisfy(_:)` operator to determine if all elements in a stream
|
||||
/// satisfy a criteria in an error-throwing predicate you provide. When this publisher
|
||||
/// receives an element, it runs the predicate against the element. If the predicate
|
||||
/// returns `false`, the publisher produces a `false` value and finishes.
|
||||
/// If the upstream publisher finishes normally, this publisher produces a `true`
|
||||
/// value and finishes. If the predicate throws an error, the publisher fails and
|
||||
/// passes the error to its downstream subscriber.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element.
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
/// The closure may throw, in which case the publisher cancels the upstream
|
||||
/// publisher and fails with the thrown error.
|
||||
/// - Returns: A publisher that publishes a Boolean value that indicates whether
|
||||
/// all received elements pass a given predicate.
|
||||
/// In the example below, an error-throwing predicate tests if each of an integer
|
||||
/// array publisher’s elements fall into the `targetRange`; the predicate throws
|
||||
/// an error if an element is zero and terminates the stream.
|
||||
///
|
||||
/// let targetRange = (-1...100)
|
||||
/// let numbers = [-1, 10, 5, 0]
|
||||
///
|
||||
/// numbers.publisher
|
||||
/// .tryAllSatisfy { anInt in
|
||||
/// guard anInt != 0 else { throw RangeError() }
|
||||
/// return targetRange.contains(anInt)
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "completion: failure(RangeError())"
|
||||
///
|
||||
/// With operators similar to `reduce(_:_:)`, this publisher produces at most one
|
||||
/// value.
|
||||
///
|
||||
/// > Note: Upon receiving any request greater than zero, this publisher requests
|
||||
/// unlimited elements from the upstream publisher.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element. Return
|
||||
/// `true` to continue, or `false` to cancel the upstream and complete. The closure
|
||||
/// may throw an error, in which case the publisher cancels the upstream publisher
|
||||
/// and fails with the thrown error.
|
||||
/// - Returns: A publisher that publishes a Boolean value that indicates whether all
|
||||
/// received elements pass a given predicate.
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryAllSatisfy<Self> {
|
||||
|
||||
@@ -10,8 +10,34 @@ extension Publisher {
|
||||
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes
|
||||
/// all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but
|
||||
/// do not impact performance of shipping code.
|
||||
/// Use `assertNoFailure()` for internal sanity checks that are active during testing.
|
||||
/// However, it is important to note that, like its Swift counterpart
|
||||
/// `fatalError(_:)`, the `assertNoFailure()` operator asserts a fatal exception when
|
||||
/// triggered in both development/testing _and_ shipping versions of code.
|
||||
///
|
||||
/// In the example below, a `CurrentValueSubject` publishes the initial and second
|
||||
/// values successfully. The third value, containing a `genericSubjectError`, causes
|
||||
/// the `assertNoFailure()` operator to assert a fatal exception stopping the process:
|
||||
///
|
||||
/// public enum SubjectError: Error {
|
||||
/// case genericSubjectError
|
||||
/// }
|
||||
///
|
||||
/// let subject = CurrentValueSubject<String, Error>("initial value")
|
||||
/// subject
|
||||
/// .assertNoFailure()
|
||||
/// .sink(receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0).") }
|
||||
/// )
|
||||
///
|
||||
/// subject.send("second value")
|
||||
/// subject.send(completion: .failure(SubjectError.genericSubjectError))
|
||||
///
|
||||
/// // Prints:
|
||||
/// // value: initial value.
|
||||
/// // value: second value.
|
||||
/// // The process then terminates in the debugger as the assertNoFailure
|
||||
/// // operator catches the genericSubjectError.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - prefix: A string used at the beginning of the fatal error message.
|
||||
|
||||
@@ -11,13 +11,19 @@ extension ConnectablePublisher {
|
||||
/// publisher.
|
||||
///
|
||||
/// Use `autoconnect()` to simplify working with `ConnectablePublisher` instances,
|
||||
/// such as those created with `makeConnectable()`.
|
||||
/// such as `TimerPublisher` in `OpenCombineFoundation`.
|
||||
///
|
||||
/// let autoconnectedPublisher = somePublisher
|
||||
/// .makeConnectable()
|
||||
/// In the following example, the `Timer.publish()` operator creates
|
||||
/// a `TimerPublisher`, which is a `ConnectablePublisher`. As a result, subscribers
|
||||
/// don’t receive any values until after a call to `connect()`.
|
||||
/// For convenience when working with a single subscriber, the `.autoconnect()`
|
||||
/// operator performs the `connect()` call when attached to by the subscriber.
|
||||
///
|
||||
/// cancellable = Timer.publish(every: 1, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .subscribe(someSubscriber)
|
||||
///
|
||||
/// .sink { date in
|
||||
/// print ("Date now: \(date)")
|
||||
/// }
|
||||
/// - Returns: A publisher which automatically connects to its upstream connectable
|
||||
/// publisher.
|
||||
public func autoconnect() -> Publishers.Autoconnect<Self> {
|
||||
@@ -27,12 +33,12 @@ extension ConnectablePublisher {
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that automatically connects and disconnects from this connectable
|
||||
/// publisher.
|
||||
/// A publisher that automatically connects to an upstream connectable publisher.
|
||||
///
|
||||
/// This publisher calls `connect()` on the upstream `ConnectablePublisher` when first
|
||||
/// attached to by a subscriber.
|
||||
public class Autoconnect<Upstream: ConnectablePublisher>: Publisher {
|
||||
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
#if !WASI
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
@@ -14,9 +16,29 @@ extension Publisher {
|
||||
/// Raises a debugger signal when a provided closure needs to stop the process in
|
||||
/// the debugger.
|
||||
///
|
||||
/// When any of the provided closures returns `true`, this publisher raises
|
||||
/// the `SIGTRAP` signal to stop the process in the debugger.
|
||||
/// Otherwise, this publisher passes through values and completions as-is.
|
||||
/// Use `breakpoint(receiveSubscription:receiveOutput:receiveCompletion:)` to examine
|
||||
/// one or more stages of the subscribe/publish/completion process and stop in
|
||||
/// the debugger, based on conditions you specify. When any of the provided closures
|
||||
/// returns `true`, this operator raises the `SIGTRAP` signal to stop the process
|
||||
/// in the debugger. Otherwise, this publisher passes through values and completions
|
||||
/// as-is.
|
||||
///
|
||||
/// In the example below, a `PassthroughSubject` publishes strings to a breakpoint
|
||||
/// republisher. When the breakpoint receives the string “`DEBUGGER`”, it returns
|
||||
/// `true`, which stops the app in the debugger.
|
||||
///
|
||||
/// let publisher = PassthroughSubject<String?, Never>()
|
||||
/// cancellable = publisher
|
||||
/// .breakpoint(
|
||||
/// receiveOutput: { value in return value == "DEBUGGER" }
|
||||
/// )
|
||||
/// .sink { print("\(String(describing: $0))" , terminator: " ") }
|
||||
///
|
||||
/// publisher.send("DEBUGGER")
|
||||
///
|
||||
/// // Prints: "error: Execution was interrupted, reason: signal SIGTRAP."
|
||||
/// // Depending on your specific environment, the console messages may
|
||||
/// // also include stack trace information, which is not shown here.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveSubscription: A closure that executes when when the publisher receives
|
||||
@@ -44,19 +66,46 @@ extension Publisher {
|
||||
/// Raises a debugger signal upon receiving a failure.
|
||||
///
|
||||
/// When the upstream publisher fails with an error, this publisher raises
|
||||
/// the `SIGTRAP` signal, which stops the process in the debugger.
|
||||
/// Otherwise, this publisher passes through values and completions as-is.
|
||||
/// the `SIGTRAP` signal, which stops the process in the debugger. Otherwise, this
|
||||
/// publisher passes through values and completions as-is.
|
||||
///
|
||||
/// In this example a `PassthroughSubject` publishes strings, but its downstream
|
||||
/// `Publisher/tryMap(_:)` operator throws an error. This sends the error downstream
|
||||
/// as a `Subscribers.Completion.failure(_:)`. The `breakpointOnError()`
|
||||
/// operator receives this completion and stops the app in the debugger.
|
||||
///
|
||||
/// struct CustomError : Error {}
|
||||
/// let publisher = PassthroughSubject<String?, Error>()
|
||||
/// cancellable = publisher
|
||||
/// .tryMap { stringValue in
|
||||
/// throw CustomError()
|
||||
/// }
|
||||
/// .breakpointOnError()
|
||||
/// .sink(
|
||||
/// receiveCompletion: { completion in
|
||||
/// print("Completion: \(String(describing: completion))")
|
||||
/// },
|
||||
/// receiveValue: { aValue in
|
||||
/// print("Result: \(String(describing: aValue))")
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// publisher.send("TEST DATA")
|
||||
///
|
||||
/// // Prints: "error: Execution was interrupted, reason: signal SIGTRAP."
|
||||
/// // Depending on your specific environment, the console messages may
|
||||
/// // also include stack trace information, which is not shown here.
|
||||
///
|
||||
/// - Returns: A publisher that raises a debugger signal upon receiving a failure.
|
||||
public func breakpointOnError() -> Publishers.Breakpoint<Self> {
|
||||
return breakpoint { completion in
|
||||
return breakpoint(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .finished:
|
||||
return false
|
||||
case .failure:
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,12 +221,11 @@ extension Publishers.Breakpoint {
|
||||
var description: String { return "Breakpoint" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(
|
||||
("upstream", breakpoint.upstream)
|
||||
)
|
||||
return Mirror(self, children: children)
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !WASI
|
||||
|
||||
@@ -8,9 +8,20 @@
|
||||
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.
|
||||
///
|
||||
/// Use `buffer(size:prefetch:whenFull:)` to collect a specific number of elements
|
||||
/// from an upstream publisher before republishing them to the downstream subscriber
|
||||
/// according to the `Publishers.BufferingStrategy` and `Publishers.PrefetchStrategy`
|
||||
/// strategy you specify.
|
||||
///
|
||||
/// If the publisher completes before reaching the `size` threshold, it buffers
|
||||
/// the elements and publishes them downstream prior to completion.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - size: The maximum number of elements to store.
|
||||
/// - prefetch: The strategy to initially populate the buffer.
|
||||
/// - whenFull: The action to take when the buffer becomes full.
|
||||
/// - Returns: A publisher that buffers elements received from an upstream publisher.
|
||||
public func buffer(
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
@@ -26,37 +37,33 @@ extension Publisher {
|
||||
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.
|
||||
///
|
||||
/// This strategy starts by making a demand equal to the buffer’s size from
|
||||
/// the upstream when the subscriber first connects. Afterwards, it continues
|
||||
/// to demand elements from the upstream to try to keep the buffer full.
|
||||
case keepFull
|
||||
|
||||
/// A strategy that avoids prefetching and instead performs requests
|
||||
/// on demand.
|
||||
/// A strategy that avoids prefetching and instead performs requests on demand.
|
||||
///
|
||||
/// This strategy just forwards the downstream’s requests to the upstream
|
||||
/// publisher.
|
||||
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.
|
||||
/// A strategy that handles exhaustion of a buffer’s capacity.
|
||||
public enum BufferingStrategy<Failure: Error> {
|
||||
|
||||
/// When full, discard the newly-received element without buffering it.
|
||||
/// When the buffer is full, discard the newly received element.
|
||||
case dropNewest
|
||||
|
||||
/// When full, remove the least recently-received element from the buffer.
|
||||
/// When the buffer is full, discard the oldest element in the buffer.
|
||||
case dropOldest
|
||||
|
||||
/// When full, execute the closure to provide a custom error.
|
||||
/// When the buffer is full, execute the closure to provide a custom error.
|
||||
case customError(() -> Failure)
|
||||
}
|
||||
|
||||
@@ -98,7 +105,11 @@ extension Publishers {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, buffer: self))
|
||||
let inner = Inner(downstream: subscriber,
|
||||
size: size,
|
||||
prefetch: prefetch,
|
||||
whenFull: whenFull)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,17 +131,19 @@ extension Publishers.Buffer {
|
||||
|
||||
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 let size: Int
|
||||
|
||||
private let prefetch: Publishers.PrefetchStrategy // keepFull is 0x0
|
||||
|
||||
private let whenFull: Publishers.BufferingStrategy<Failure>
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
@@ -142,8 +155,14 @@ extension Publishers.Buffer {
|
||||
|
||||
private var terminal: Subscribers.Completion<Failure>?
|
||||
|
||||
init(downstream: Downstream, buffer: Publishers.Buffer<Upstream>) {
|
||||
state = .ready(buffer, downstream)
|
||||
init(downstream: Downstream,
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Failure>) {
|
||||
self.size = size
|
||||
self.prefetch = prefetch
|
||||
self.whenFull = whenFull
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -152,18 +171,18 @@ extension Publishers.Buffer {
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(buffer, downstream) = state else {
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(buffer, downstream, subscription)
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
|
||||
let upstreamDemand: Subscribers.Demand
|
||||
switch buffer.prefetch {
|
||||
switch prefetch {
|
||||
case .keepFull:
|
||||
upstreamDemand = .max(buffer.size)
|
||||
upstreamDemand = .max(size)
|
||||
case .byRequest:
|
||||
upstreamDemand = .unlimited
|
||||
}
|
||||
@@ -173,14 +192,14 @@ extension Publishers.Buffer {
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(buffer, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
switch terminal {
|
||||
case nil, .finished?:
|
||||
if values.count >= buffer.size {
|
||||
switch buffer.whenFull {
|
||||
if values.count >= size {
|
||||
switch whenFull {
|
||||
case .dropNewest:
|
||||
lock.unlock()
|
||||
return drain()
|
||||
@@ -216,7 +235,7 @@ extension Publishers.Buffer {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -227,13 +246,16 @@ extension Publishers.Buffer {
|
||||
return
|
||||
}
|
||||
|
||||
// Request the number of items just enough to fill the buffer.
|
||||
subscription.request(drain() + demand)
|
||||
let more = drain()
|
||||
if more != .none {
|
||||
// Request the number of items just enough to fill the buffer.
|
||||
subscription.request(more)
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -247,7 +269,7 @@ extension Publishers.Buffer {
|
||||
var upstreamDemand = Subscribers.Demand.none
|
||||
lock.lock()
|
||||
while true {
|
||||
guard case let .subscribed(buffer, downstream, _) = state else {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return upstreamDemand
|
||||
}
|
||||
@@ -296,7 +318,7 @@ extension Publishers.Buffer {
|
||||
additionalUpstreamDemand += 1
|
||||
}
|
||||
|
||||
if buffer.prefetch == .keepFull {
|
||||
if prefetch == .keepFull {
|
||||
upstreamDemand += additionalUpstreamDemand
|
||||
}
|
||||
|
||||
|
||||
@@ -13,27 +13,30 @@ 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
|
||||
/// }
|
||||
/// }
|
||||
/// Use `catch()` to replace an error from an upstream publisher with a new publisher.
|
||||
///
|
||||
/// In the example below, the `catch()` operator handles the `SimpleError` thrown by
|
||||
/// the upstream publisher by replacing the error with a `Just` publisher. This
|
||||
/// continues the stream by publishing a single value and completing normally.
|
||||
///
|
||||
/// struct SimpleError: Error {}
|
||||
/// let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryLast(where: {
|
||||
/// guard $0 != 0 else { throw SimpleError() }
|
||||
/// return true
|
||||
/// })
|
||||
/// .catch { error in
|
||||
/// Just(-1)
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
/// // Prints: -1
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// - SeeAlso: `replaceError`
|
||||
/// - 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
|
||||
@@ -47,13 +50,48 @@ extension Publisher {
|
||||
}
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another
|
||||
/// publisher or `throw`ing a new error.
|
||||
/// publisher or throwing 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.
|
||||
/// Use `tryCatch(_:)` to decide how to handle from an upstream publisher by either
|
||||
/// replacing the publisher with a new publisher, or throwing a new error.
|
||||
///
|
||||
/// In the example below, an array publisher emits values that a `tryMap(_:)` operator
|
||||
/// evaluates to ensure the values are greater than zero. If the values aren’t greater
|
||||
/// than zero, the operator throws an error to the downstream subscriber to let it
|
||||
/// know there was a problem. The subscriber, `tryCatch(_:)`, replaces the error with
|
||||
/// a new publisher using ``Just`` to publish a final value before the stream ends
|
||||
/// normally.
|
||||
///
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// var numbers = [5, 4, 3, 2, 1, -1, 7, 8, 9, 10]
|
||||
///
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMap { v in
|
||||
/// if v > 0 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
/// .tryCatch { error in
|
||||
/// Just(0) // Send a final value before completing normally.
|
||||
/// // Alternatively, throw a new error to terminate the stream.
|
||||
/// }
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0).") },
|
||||
/// receiveValue: { print ("Received \($0).") })
|
||||
/// // Received 5.
|
||||
/// // Received 4.
|
||||
/// // Received 3.
|
||||
/// // Received 2.
|
||||
/// // Received 1.
|
||||
/// // Received 0.
|
||||
/// // Completion: finished.
|
||||
///
|
||||
/// - Parameter handler: A throwing closure that accepts the upstream failure as
|
||||
/// input. This closure can either replace the upstream publisher with a new one,
|
||||
/// or throw a new error to the downstream subscriber.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
/// the failed publisher with another publisher, or an error.
|
||||
public func tryCatch<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) throws -> NewPublisher
|
||||
) -> Publishers.TryCatch<Self, NewPublisher>
|
||||
@@ -103,8 +141,12 @@ extension Publishers {
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher or optionally producing a new error.
|
||||
/// A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher or producing a new error.
|
||||
///
|
||||
/// Because this publisher’s handler can throw an error, `Publishers.TryCatch` defines
|
||||
/// its `Failure` type as `Error`. This is different from `Publishers.Catch`, which
|
||||
/// gets its failure type from the replacement publisher.
|
||||
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
@@ -112,10 +154,21 @@ extension Publishers {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and either returns
|
||||
/// a publisher to replace the upstream publisher or throws an error.
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher or by throwing an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and either
|
||||
/// returns a publisher to replace the upstream publisher. If this closure
|
||||
/// throws an error, the publisher terminates with the thrown error.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
|
||||
@@ -10,13 +10,30 @@ extension Publisher {
|
||||
/// Collects all received elements, and emits a single array of the collection when
|
||||
/// the upstream publisher finishes.
|
||||
///
|
||||
/// Use `collect()` to gather elements into an array that the operator emits after
|
||||
/// the upstream publisher finishes.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
///
|
||||
/// This publisher requests an unlimited number of elements from the upstream
|
||||
/// publisher. It only sends the collected array to its downstream after a request
|
||||
/// whose demand is greater than 0 items.
|
||||
/// Note: This publisher uses an unbounded amount of memory to store the received
|
||||
/// values.
|
||||
/// publisher and uses an unbounded amount of memory to store the received values.
|
||||
/// The publisher may exert memory pressure on the system for very large sets of
|
||||
/// elements.
|
||||
///
|
||||
/// The `collect()` operator only sends the collected array to its downstream receiver
|
||||
/// after a request whose demand is greater than 0 items. Otherwise, `collect()` waits
|
||||
/// until it receives a non-zero request.
|
||||
///
|
||||
/// In the example below, an Integer range is a publisher that emits an array of
|
||||
/// integers:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .collect()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||
///
|
||||
/// - Returns: A publisher that collects all received items and returns them as
|
||||
/// an array upon completion.
|
||||
|
||||
@@ -10,12 +10,25 @@ extension Publisher {
|
||||
/// Collects up to the specified number of elements, and then emits a single array of
|
||||
/// the collection.
|
||||
///
|
||||
/// If the upstream publisher finishes before filling the buffer, this publisher sends
|
||||
/// an array of all the items it has received. This may be fewer than `count`
|
||||
/// elements.
|
||||
/// Use `collect(_:)` to emit arrays of at most `count` elements from an upstream
|
||||
/// publisher. If the upstream publisher finishes before collecting the specified
|
||||
/// number of elements, the publisher sends an array of only the items it received
|
||||
/// This may be fewer than `count` elements.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
/// Note: When this publisher receives a request for `.max(n)` elements, it requests
|
||||
///
|
||||
/// In the example below, the `collect(_:)` operator emits one partial and two full
|
||||
/// arrays based on the requested collection size of `5`:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .collect(5)
|
||||
/// .sink { print("\($0), terminator: " "") }
|
||||
///
|
||||
/// // Prints "[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [10] "
|
||||
///
|
||||
/// > Note: When this publisher receives a request for `.max(n)` elements, it requests
|
||||
/// `.max(count * n)` from the upstream publisher.
|
||||
///
|
||||
/// - Parameter count: The maximum number of received elements to buffer before
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.CombineLatest.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.12.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')]
|
||||
|
||||
def make_publisher_name(arity):
|
||||
return suffix_variadic('CombineLatest', arity, arity - 1)
|
||||
|
||||
def make_upstream_types(arity, start=0):
|
||||
return [str(c) for c in string.ascii_uppercase[start:(start + arity)]]
|
||||
|
||||
def make_upstream_generic_constraints(upstream_types, first_is_self=False):
|
||||
|
||||
format_string = '{0}Failure == {1}.Failure'
|
||||
|
||||
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_combine_latest_method(arity, transform):
|
||||
arg_count = arity - 1
|
||||
declaration_format = """\
|
||||
public func combineLatest<{}>(
|
||||
{}
|
||||
) -> {}
|
||||
where {}\
|
||||
"""
|
||||
upstream_types = make_upstream_types(arg_count, 15)
|
||||
method_generic_params = \
|
||||
[upstream_type + ': Publisher' for upstream_type in upstream_types]
|
||||
if transform:
|
||||
method_generic_params.append('Result')
|
||||
cs_method_generic_params = ', '.join(method_generic_params)
|
||||
method_args = ['_ other: P'] if arg_count == 1 \
|
||||
else ['_ publisher{}: {}'.format(i + 1, upstream_types[i]) \
|
||||
for i in range(arg_count)]
|
||||
if transform:
|
||||
output_types = ['Output'] + ['{}.Output'.format(upstream_type) \
|
||||
for upstream_type in upstream_types]
|
||||
cs_output_types = ', '.join(output_types)
|
||||
method_args \
|
||||
.append('_ transform: @escaping ({}) -> Result'.format(cs_output_types))
|
||||
cs_method_args = ',\n '.join(method_args)
|
||||
|
||||
publisher_generic_params = ['Self'] + upstream_types
|
||||
|
||||
cs_publisher_generic_params = ', '.join(publisher_generic_params)
|
||||
|
||||
publisher_name = 'Publishers.{}<{}>'.format(make_publisher_name(arity),
|
||||
cs_publisher_generic_params)
|
||||
|
||||
if transform:
|
||||
publisher_name = 'Publishers.Map<{}, Result>'.format(publisher_name)
|
||||
|
||||
generic_constraints = make_upstream_generic_constraints(upstream_types,
|
||||
first_is_self=True)
|
||||
|
||||
cs_generic_constraints = ',\n '.join(generic_constraints)
|
||||
|
||||
declaration = declaration_format.format(cs_method_generic_params,
|
||||
cs_method_args,
|
||||
publisher_name,
|
||||
cs_generic_constraints)
|
||||
|
||||
return indent(declaration, 4)
|
||||
}%
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
// swiftlint:disable large_tuple
|
||||
|
||||
// MARK: - CombineLatest methods on Publisher
|
||||
|
||||
extension Publisher {
|
||||
|
||||
% for arity, _, _ in instantiations:
|
||||
%
|
||||
% argument_names = ['other'] \
|
||||
% if arity == 2 else ['publisher{}'.format(i) for i in range(1, arity)]
|
||||
% doc_cardinal = 'an additional publisher' if arity == 2 \
|
||||
% else '{} additional publishers'.format(instantiations[arity - 3][1])
|
||||
/// Subscribes to ${doc_cardinal} and publishes a tuple upon
|
||||
/// receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity - 1):
|
||||
% param_doc = 'Another' if arity == 2 else instantiations[i][2]
|
||||
/// - ${argument_names[i]}: ${param_doc} publisher to combine with this one.
|
||||
% end
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
${declare_combine_latest_method(arity, transform=False)}
|
||||
{
|
||||
return .init(self, ${', '.join(argument_names)})
|
||||
}
|
||||
|
||||
/// Subscribes to ${doc_cardinal} and invokes a closure
|
||||
/// upon receiving output from either publisher.
|
||||
///
|
||||
/// The combined publisher passes through any requests to *all* upstream publishers.
|
||||
/// However, it still obeys the demand-fulfilling rule of only sending the request
|
||||
/// amount downstream. If the demand isn’t `.unlimited`, it drops values from upstream
|
||||
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
|
||||
/// holds the most recent value in each buffer.
|
||||
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
|
||||
/// publisher never publishes a value, this publisher never finishes.
|
||||
/// If any of the combined publishers terminates with a failure, this publisher also
|
||||
/// fails.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity - 1):
|
||||
% param_doc = 'Another' if arity == 2 else instantiations[i][2]
|
||||
/// - ${argument_names[i]}: ${param_doc} publisher to combine with this one.
|
||||
% end
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that receives and combines elements from this and another
|
||||
/// publisher.
|
||||
${declare_combine_latest_method(arity, transform=True)}
|
||||
{
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
return Publishers.${publisher_name}(self, ${', '.join(argument_names)}).map {
|
||||
transform(${', '.join(['${}'.format(i) for i in range(arity)])})
|
||||
}
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
// MARK: - CombineLatest 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 = ', '.join(upstream_generic_params)
|
||||
%
|
||||
% output_types = [upstream_type + '.Output' for upstream_type in upstream_types]
|
||||
%
|
||||
% cs_output_types = ', '.join(output_types)
|
||||
%
|
||||
% 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 that receives and combines the latest elements from ${cardinal}
|
||||
/// publishers.
|
||||
public struct ${publisher_name}<${cs_upstream_generic_params}>
|
||||
: Publisher
|
||||
where ${cs_upstream_generic_constraints}
|
||||
{
|
||||
public typealias Output = (${cs_output_types})
|
||||
|
||||
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 Downstream.Failure == Failure,
|
||||
Downstream.Input == Output
|
||||
{
|
||||
% cs_indented_output_types = (',\n' + (50 * ' ')).join(output_types)
|
||||
typealias Inner = CombineLatest${arity}Inner<${cs_indented_output_types},
|
||||
Failure,
|
||||
Downstream>
|
||||
let inner = Inner(downstream: subscriber, upstreamCount: ${arity})
|
||||
% for i in range(arity):
|
||||
${self_fields[i]}.subscribe(Inner.Side(index: ${i}, combiner: inner))
|
||||
% end
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
// MARK: - Equatable conformances
|
||||
% for arity, _, _ 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
|
||||
|
||||
// MARK: - Inners
|
||||
% for arity, _, _ in instantiations:
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% upstream_types = make_upstream_types(arity)
|
||||
%
|
||||
% input_types = ['Input{}'.format(i) for i in range(arity)]
|
||||
%
|
||||
% converters = ['values[{}] as! {}'.format(i, input_types[i]) for i in range(arity)]
|
||||
% output_type = '({})'.format(', '.join(input_types))
|
||||
|
||||
private final class CombineLatest${arity}Inner<${(',\n' + (40 * ' ')).join(input_types)},
|
||||
Failure,
|
||||
Downstream: Subscriber>
|
||||
: AbstractCombineLatest<${output_type}, Failure, Downstream>
|
||||
where Downstream.Input == ${output_type},
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
override func convert(values: [Any?]) -> (${', '.join(input_types)}) {
|
||||
return (${',\n '.join(converters)})
|
||||
}
|
||||
}
|
||||
% end
|
||||
@@ -7,29 +7,79 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Calls a closure with each received element and publishes any returned
|
||||
/// optional that has a value.
|
||||
/// Calls a closure with each received element and publishes any returned optional
|
||||
/// that has a value.
|
||||
///
|
||||
/// - Parameter transform: A closure that receives a value and returns
|
||||
/// an optional value.
|
||||
/// - Returns: A publisher that republishes all non-`nil` results of calling
|
||||
/// the transform closure.
|
||||
/// OpenCombine’s `compactMap(_:)` operator performs a function similar to that of
|
||||
/// `compactMap(_:)` in the Swift standard library: the `compactMap(_:)` operator in
|
||||
/// OpenCombine removes `nil` elements in a publisher’s stream and republishes
|
||||
/// non-`nil` elements to the downstream subscriber.
|
||||
///
|
||||
/// The example below uses a range of numbers as the source for a collection based
|
||||
/// publisher. The `compactMap(_:)` operator consumes each element from the `numbers`
|
||||
/// publisher attempting to access the dictionary using the element as the key.
|
||||
/// If the example’s dictionary returns a `nil`, due to a non-existent key,
|
||||
/// `compactMap(_:)` filters out the `nil` (missing) elements.
|
||||
///
|
||||
/// let numbers = (0...5)
|
||||
/// let romanNumeralDict: [Int : String] =
|
||||
/// [1: "I", 2: "II", 3: "III", 5: "V"]
|
||||
///
|
||||
/// cancellable = numbers.publisher
|
||||
/// .compactMap { romanNumeralDict[$0] }
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "I II III V"
|
||||
///
|
||||
/// - Parameter transform: A closure that receives a value and returns an optional
|
||||
/// value.
|
||||
/// - Returns: Any non-`nil` optional results of the calling the supplied closure.
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult?
|
||||
) -> Publishers.CompactMap<Self, ElementOfResult> {
|
||||
return .init(upstream: self, transform: transform)
|
||||
}
|
||||
|
||||
/// Calls an error-throwing closure with each received element and publishes
|
||||
/// any returned optional that has a value.
|
||||
/// Calls an error-throwing closure with each received element and publishes any
|
||||
/// returned optional that has a value.
|
||||
///
|
||||
/// If the closure throws an error, the publisher cancels the upstream and sends
|
||||
/// the thrown error to the downstream receiver as a `Failure`.
|
||||
/// Use `tryCompactMap(_:)` to remove `nil` elements from a publisher’s stream based
|
||||
/// on an error-throwing closure you provide. If the closure throws an error,
|
||||
/// the publisher cancels the upstream publisher and sends the thrown error to
|
||||
/// the downstream subscriber as a `Publisher.Failure`.
|
||||
///
|
||||
/// - Parameter transform: an error-throwing closure that receives a value
|
||||
/// and returns an optional value.
|
||||
/// - Returns: A publisher that republishes all non-`nil` results of calling
|
||||
/// the `transform` closure.
|
||||
/// The following example uses an array of numbers as the source for
|
||||
/// a collection-based publisher. A `tryCompactMap(_:)` operator consumes each integer
|
||||
/// from the publisher and uses a dictionary to transform the numbers from its Arabic
|
||||
/// to Roman numerals, as an optional `String`.
|
||||
///
|
||||
/// If the closure called by `tryCompactMap(_:)` fails to look up a Roman numeral,
|
||||
/// it returns the optional String `(unknown)`.
|
||||
///
|
||||
/// If the closure called by `tryCompactMap(_:)` determines the input is `0`, it
|
||||
/// throws an error. The `tryCompactMap(_:)` operator catches this error and stops
|
||||
/// publishing, sending a `Subscribers.Completion.failure(_:)` that wraps the error.
|
||||
///
|
||||
/// struct ParseError: Error {}
|
||||
/// func romanNumeral(from: Int) throws -> String? {
|
||||
/// let romanNumeralDict: [Int : String] =
|
||||
/// [1: "I", 2: "II", 3: "III", 4: "IV", 5: "V"]
|
||||
/// guard from != 0 else { throw ParseError() }
|
||||
/// return romanNumeralDict[from]
|
||||
/// }
|
||||
/// let numbers = [6, 5, 4, 3, 2, 1, 0]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryCompactMap { try romanNumeral(from: $0) }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("\($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "(Unknown) V IV III II I failure(ParseError())"
|
||||
///
|
||||
/// - Parameter transform: An error-throwing closure that receives a value and returns
|
||||
/// an optional value.
|
||||
/// - Returns: Any non-`nil` optional results of calling the supplied closure.
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
|
||||
@@ -122,23 +172,48 @@ extension Publishers {
|
||||
}
|
||||
|
||||
extension Publishers.CompactMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Output?>
|
||||
where Downstream.Failure == Upstream.Failure, Downstream.Input == Output
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Output?, Downstream.Failure> {
|
||||
return .continue(filter(newValue))
|
||||
private let downstream: Downstream
|
||||
private let filter: (Input) -> Downstream.Input?
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(downstream: Downstream, filter: @escaping (Input) -> Downstream.Input?) {
|
||||
self.downstream = downstream
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
override var description: String { return "CompactMap" }
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if let output = filter(input) {
|
||||
return downstream.receive(output)
|
||||
}
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "CompactMap" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +226,6 @@ extension Publishers.TryCompactMap {
|
||||
(Upstream.Output) throws -> Output?>
|
||||
where Downstream.Failure == Error, Downstream.Input == Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Output?, Error> {
|
||||
|
||||
@@ -10,6 +10,20 @@ extension Publisher where Output: Comparable {
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// Use `min()` to find the minimum value in a stream of elements from
|
||||
/// an upstream publisher.
|
||||
///
|
||||
/// In the example below, the `min()` operator emits a value when the publisher
|
||||
/// finishes, that value is the minimum of the values received from upstream, which
|
||||
/// is `-1`.
|
||||
///
|
||||
/// let numbers = [-1, 0, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .min()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "-1"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
@@ -22,6 +36,20 @@ extension Publisher where Output: Comparable {
|
||||
/// Publishes the maximum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// Use `max()` to determine the maximum value in the stream of elements from
|
||||
/// an upstream publisher.
|
||||
///
|
||||
/// In the example below, the `max()` operator emits a value when the publisher
|
||||
/// finishes, that value is the maximum of the values received from upstream, which
|
||||
/// is `10`.
|
||||
///
|
||||
/// let numbers = [0, 10, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .max()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "10"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
@@ -37,11 +65,36 @@ extension Publisher {
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// Use `min(by:)` to determine the minimum value in the stream of elements from
|
||||
/// an upstream publisher using a comparison operation you specify.
|
||||
///
|
||||
/// This operator is useful when the value received from the upstream publisher isn’t
|
||||
/// `Comparable`.
|
||||
///
|
||||
/// In the example below an array publishes enumeration elements representing playing
|
||||
/// card ranks. The `min(by:)` operator compares the current and next elements using
|
||||
/// the `rawValue` property of each enumeration value in the user supplied closure and
|
||||
/// prints the minimum value found after publishing all of the elements.
|
||||
///
|
||||
/// enum Rank: Int {
|
||||
/// case ace = 1, two, three, four, five, six, seven, eight, nine,
|
||||
/// ten, jack, queen, king
|
||||
/// }
|
||||
///
|
||||
/// let cards: [Rank] = [.five, .queen, .ace, .eight, .king]
|
||||
/// cancellable = cards.publisher
|
||||
/// .min {
|
||||
/// return $0.rawValue < $1.rawValue
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "ace"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they are in increasing order.
|
||||
/// `true` if they’re in increasing order.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min(
|
||||
@@ -50,15 +103,39 @@ extension Publisher {
|
||||
return max(by: { areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, using the
|
||||
/// provided error-throwing closure to order the items.
|
||||
/// Publishes the minimum value received from the upstream publisher, using
|
||||
/// the provided error-throwing closure to order the items.
|
||||
///
|
||||
/// Use `tryMin(by:)` to determine the minimum value of elements received from
|
||||
/// the upstream publisher using an error-throwing closure you specify.
|
||||
///
|
||||
/// In the example below, an array publishes elements. The `tryMin(by:)` operator
|
||||
/// executes the error-throwing closure that throws when the `first` element is an odd
|
||||
/// number, terminating the publisher.
|
||||
///
|
||||
/// struct IllegalValueError: Error {}
|
||||
///
|
||||
/// let numbers: [Int] = [0, 10, 6, 13, 22, 22]
|
||||
/// numbers.publisher
|
||||
/// .tryMin { first, second -> Bool in
|
||||
/// if (first % 2 != 0) {
|
||||
/// throw IllegalValueError()
|
||||
/// }
|
||||
/// return first < second
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "completion: failure(IllegalValueError())"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they are in increasing order. If this closure throws, the
|
||||
/// publisher terminates with a `Failure`.
|
||||
/// and returns `true` if they’re in increasing order. If this closure throws,
|
||||
/// the publisher terminates with a `Subscribers.Completion.failure(_:)`.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMin(
|
||||
@@ -67,14 +144,36 @@ extension Publisher {
|
||||
return tryMax(by: { try areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided ordering closure.
|
||||
/// Publishes the maximum value received from the upstream publisher, using
|
||||
/// the provided ordering closure.
|
||||
///
|
||||
/// Use `max(by:)` to determine the maximum value of elements received from
|
||||
/// the upstream publisher based on an ordering closure you specify.
|
||||
///
|
||||
/// In the example below, an array publishes enumeration elements representing playing
|
||||
/// card ranks. The `max(by:)` operator compares the current and next elements using
|
||||
/// the `rawValue` property of each enumeration value in the user supplied closure and
|
||||
/// prints the maximum value found after publishing all of the elements.
|
||||
///
|
||||
/// enum Rank: Int {
|
||||
/// case ace = 1, two, three, four, five, six, seven, eight, nine,
|
||||
/// ten, jack, queen, king
|
||||
/// }
|
||||
///
|
||||
/// let cards: [Rank] = [.five, .queen, .ace, .eight, .jack]
|
||||
/// cancellable = cards.publisher
|
||||
/// .max {
|
||||
/// return $0.rawValue > $1.rawValue
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "queen"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they are in increasing order.
|
||||
/// `true` if they’re in increasing order.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max(
|
||||
@@ -83,16 +182,42 @@ extension Publisher {
|
||||
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided error-throwing closure to order the items.
|
||||
/// Publishes the maximum value received from the upstream publisher, using
|
||||
/// the provided error-throwing closure to order the items.
|
||||
///
|
||||
/// Use `tryMax(by:)` to determine the maximum value of elements received from
|
||||
/// the upstream publisher using an error-throwing closure you specify.
|
||||
///
|
||||
/// In the example below, an array publishes elements. The `tryMax(by:)` operator
|
||||
/// executes the error-throwing closure that throws when the `first` element is
|
||||
/// an odd number, terminating the publisher.
|
||||
///
|
||||
/// struct IllegalValueError: Error {}
|
||||
///
|
||||
/// let numbers: [Int] = [0, 10, 6, 13, 22, 22]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMax { first, second -> Bool in
|
||||
/// if (first % 2 != 0) {
|
||||
/// throw IllegalValueError()
|
||||
/// }
|
||||
/// return first > second
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: completion: failure(IllegalValueError())
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they are in increasing order. If this closure throws, the
|
||||
/// publisher terminates with a `Failure`.
|
||||
/// and returns `true` if they’re in increasing order. If this closure throws,
|
||||
/// the publisher terminates with a ``Subscribers/Completion/failure(_:)``.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
|
||||
@@ -7,7 +7,20 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Prefixes a `Publisher`'s output with the specified sequence.
|
||||
/// Prefixes a publisher’s output with the specified values.
|
||||
///
|
||||
/// Use `prepend(_:)` when you need to prepend specific elements before the output
|
||||
/// of a publisher.
|
||||
///
|
||||
/// In the example below, the `prepend(_:)` operator publishes the provided elements
|
||||
/// before republishing all elements from `dataElements`:
|
||||
///
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .prepend(0, 1, 255)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
|
||||
///
|
||||
/// - Parameter elements: The elements to publish before this publisher’s elements.
|
||||
/// - Returns: A publisher that prefixes the specified elements prior to this
|
||||
@@ -18,7 +31,21 @@ extension Publisher {
|
||||
return prepend(elements)
|
||||
}
|
||||
|
||||
/// Prefixes a `Publisher`'s output with the specified sequence.
|
||||
/// Prefixes a publisher’s output with the specified sequence.
|
||||
///
|
||||
/// Use `prepend(_:)` to publish values from two publishers when you need to prepend
|
||||
/// one publisher’s elements to another.
|
||||
///
|
||||
/// In this example the `/prepend(_:)-v9sb` operator publishes the provided sequence
|
||||
/// before republishing all elements from `dataElements`:
|
||||
///
|
||||
/// let prefixValues = [0, 1, 255]
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .prepend(prefixValues)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
|
||||
///
|
||||
/// - Parameter elements: A sequence of elements to publish before this publisher’s
|
||||
/// elements.
|
||||
@@ -32,10 +59,22 @@ extension Publisher {
|
||||
return prepend(.init(sequence: elements))
|
||||
}
|
||||
|
||||
/// Prefixes this publisher’s output with the elements emitted by the given publisher.
|
||||
/// Prefixes the output of this publisher with the elements emitted by the given
|
||||
/// publisher.
|
||||
///
|
||||
/// The resulting publisher doesn’t emit any elements until the prefixing publisher
|
||||
/// finishes.
|
||||
/// Use `prepend(_:)` to publish values from two publishers when you need to prepend
|
||||
/// one publisher’s elements to another.
|
||||
///
|
||||
/// In the example below, a publisher of `prefixValues` publishes its elements before
|
||||
/// the `dataElements` publishes its elements:
|
||||
///
|
||||
/// let prefixValues = [0, 1, 255]
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .prepend(prefixValues.publisher)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
|
||||
///
|
||||
/// - Parameter publisher: The prefixing publisher.
|
||||
/// - Returns: A publisher that prefixes the prefixing publisher’s elements prior to
|
||||
@@ -48,14 +87,51 @@ extension Publisher {
|
||||
return .init(prefix: publisher, suffix: self)
|
||||
}
|
||||
|
||||
/// Append a `Publisher`'s output with the specified sequence.
|
||||
/// Appends a publisher’s output with the specified elements.
|
||||
///
|
||||
/// Use `append(_:)` when you need to prepend specific elements after the output of
|
||||
/// a publisher.
|
||||
///
|
||||
/// In the example below, the `append(_:)` operator publishes the provided elements
|
||||
/// after republishing all elements from `dataElements`:
|
||||
///
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .append(0, 1, 255)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4 5 6 7 8 9 10 0 1 255"
|
||||
///
|
||||
///
|
||||
/// - Parameter elements: Elements to publish after this publisher’s elements.
|
||||
/// - Returns: A publisher that appends the specifiecd elements after this publisher’s
|
||||
/// elements.
|
||||
public func append(
|
||||
_ elements: Output...
|
||||
) -> Publishers.Concatenate<Self, Publishers.Sequence<[Output], Failure>> {
|
||||
return append(elements)
|
||||
}
|
||||
|
||||
/// Appends a `Publisher`'s output with the specified sequence.
|
||||
/// Appends a publisher’s output with the specified sequence.
|
||||
///
|
||||
/// Use `append(_:)` to append a sequence to the end of
|
||||
/// a publisher’s output.
|
||||
///
|
||||
/// In the example below, the `append(_:)` publisher republishes all elements from
|
||||
/// `groundTransport` until it finishes, then publishes the members of `airTransport`:
|
||||
///
|
||||
/// let groundTransport = ["car", "bus", "truck", "subway", "bicycle"]
|
||||
/// let airTransport = ["parasail", "jet", "helicopter", "rocket"]
|
||||
/// cancellable = groundTransport.publisher
|
||||
/// .append(airTransport)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "car bus truck subway bicycle parasail jet helicopter rocket"
|
||||
///
|
||||
/// - Parameter elements: A sequence of elements to publish after this publisher’s
|
||||
/// elements.
|
||||
/// - Returns: A publisher that appends the sequence of elements after this
|
||||
/// publisher’s elements.
|
||||
public func append<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Concatenate<Self, Publishers.Sequence<Elements, Failure>>
|
||||
@@ -64,12 +140,26 @@ extension Publisher {
|
||||
return append(.init(sequence: elements))
|
||||
}
|
||||
|
||||
/// Appends this publisher’s output with the elements emitted by the given publisher.
|
||||
/// Appends the output of this publisher with the elements emitted by the given
|
||||
/// publisher.
|
||||
///
|
||||
/// This operator produces no elements until this publisher finishes. It then produces
|
||||
/// this publisher’s elements, followed by the given publisher’s elements.
|
||||
/// If this publisher fails with an error, the prefixing publisher does not publish
|
||||
/// the provided publisher’s elements.
|
||||
/// Use `append(_:)` to append the output of one publisher to another.
|
||||
/// The `append(_:)` operator produces no elements until this publisher finishes.
|
||||
/// It then produces this publisher’s elements, followed by the given publisher’s
|
||||
/// elements. If this publisher fails with an error, the given publishers elements
|
||||
/// aren’t published.
|
||||
///
|
||||
/// In the example below, the `append` publisher republishes all elements from
|
||||
/// the `numbers` publisher until it finishes, then publishes all elements from
|
||||
/// the `otherNumbers` publisher:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// let otherNumbers = (25...35)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .append(otherNumbers.publisher)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4 5 6 7 8 9 10 25 26 27 28 29 30 31 32 33 34 35 "
|
||||
///
|
||||
/// - Parameter publisher: The appending publisher.
|
||||
/// - Returns: A publisher that appends the appending publisher’s elements after this
|
||||
@@ -110,8 +200,7 @@ 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)
|
||||
prefix.subscribe(Inner<Downstream>.PrefixSubscriber(inner: inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,9 +208,8 @@ extension Publishers {
|
||||
extension Publishers.Concatenate: Equatable where Prefix: Equatable, Suffix: Equatable {}
|
||||
|
||||
extension Publishers.Concatenate {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
fileprivate final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
@@ -131,22 +219,26 @@ extension Publishers.Concatenate {
|
||||
|
||||
typealias Failure = Suffix.Failure
|
||||
|
||||
fileprivate struct PrefixSubscriber {
|
||||
let inner: Inner<Downstream>
|
||||
}
|
||||
|
||||
fileprivate struct SuffixSubscriber {
|
||||
let inner: Inner<Downstream>
|
||||
}
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var prefixState = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var suffixState = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let suffix: Suffix
|
||||
|
||||
private var prefixFinished = false
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var upstream: Subscription?
|
||||
|
||||
private var expectedSubscriptions = 2
|
||||
private var pending = Subscribers.Demand.none
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, suffix: Suffix) {
|
||||
self.downstream = downstream
|
||||
self.suffix = suffix
|
||||
@@ -154,65 +246,13 @@ extension Publishers.Concatenate {
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard upstream == nil, expectedSubscriptions > 0 else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
upstream = subscription
|
||||
expectedSubscriptions -= 1
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
// Reading prefixFinished should be locked. Combine doesn't lock here.
|
||||
if prefixFinished {
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
guard case .finished = completion else {
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
prefixFinished = true // Should be locked as well?
|
||||
lock.lock()
|
||||
upstream = nil
|
||||
lock.unlock()
|
||||
suffix.subscribe(self)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
self.demand += demand
|
||||
guard let subscription = upstream else {
|
||||
pending += demand
|
||||
guard let subscription = prefixState.subscription ?? suffixState.subscription
|
||||
else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -222,27 +262,203 @@ extension Publishers.Concatenate {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard let subscription = upstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstream = nil
|
||||
let upstreamSubscription =
|
||||
prefixState.subscription ?? suffixState.subscription
|
||||
prefixState = .terminal
|
||||
suffixState = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
upstreamSubscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Concatenate" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("upstreamSubscription", upstream as Any),
|
||||
("suffix", suffix),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func prefixReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = prefixState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
prefixState = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
private func prefixReceive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = prefixState, pending != .none else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
pending -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
if newDemand == .none {
|
||||
return .none
|
||||
}
|
||||
lock.lock()
|
||||
pending += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
private func prefixReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = prefixState else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
prefixState = .terminal
|
||||
lock.unlock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
suffix.subscribe(SuffixSubscriber(inner: self))
|
||||
case .failure:
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func suffixReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = suffixState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
suffixState = .subscribed(subscription)
|
||||
let pending = self.pending
|
||||
lock.unlock()
|
||||
if pending != .none {
|
||||
subscription.request(pending)
|
||||
}
|
||||
}
|
||||
|
||||
private func suffixReceive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = suffixState else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
private func suffixReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = suffixState else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
prefixState = .terminal
|
||||
suffixState = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PrefixSubscriber conformances
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber: Subscriber {
|
||||
|
||||
fileprivate typealias Input = Suffix.Output
|
||||
|
||||
fileprivate typealias Failure = Suffix.Failure
|
||||
|
||||
fileprivate var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
inner.prefixReceive(subscription: subscription)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.prefixReceive(input)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.prefixReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber
|
||||
: CustomStringConvertible
|
||||
{
|
||||
fileprivate var description: String {
|
||||
return inner.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber
|
||||
: CustomReflectable
|
||||
{
|
||||
fileprivate var customMirror: Mirror {
|
||||
return inner.customMirror
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber
|
||||
: CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
fileprivate var playgroundDescription: Any {
|
||||
return inner.playgroundDescription
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SuffixSubscriber conformances
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber: Subscriber {
|
||||
|
||||
fileprivate typealias Input = Suffix.Output
|
||||
|
||||
fileprivate typealias Failure = Suffix.Failure
|
||||
|
||||
fileprivate var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
inner.suffixReceive(subscription: subscription)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.suffixReceive(input)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.suffixReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber
|
||||
: CustomStringConvertible
|
||||
{
|
||||
fileprivate var description: String {
|
||||
return inner.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber
|
||||
: CustomReflectable
|
||||
{
|
||||
fileprivate var customMirror: Mirror {
|
||||
return inner.customMirror
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber
|
||||
: CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
fileprivate var playgroundDescription: Any {
|
||||
return inner.playgroundDescription
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,22 @@ extension Publisher where Output: Equatable {
|
||||
|
||||
/// Publishes a Boolean value upon receiving an element equal to the argument.
|
||||
///
|
||||
/// The contains publisher consumes all received elements until the upstream publisher
|
||||
/// produces a matching element. At that point, it emits `true` and finishes normally.
|
||||
/// If the upstream finishes normally without producing a matching element,
|
||||
/// this publisher emits `false`, then finishes.
|
||||
/// Use `contains(_:)` to find the first element in an upstream that’s equal to
|
||||
/// the supplied argument. The `Publishers.Contains` publisher consumes all received
|
||||
/// elements until the upstream publisher produces a matching element. Upon finding
|
||||
/// the first match, it emits `true` and finishes normally. If the upstream finishes
|
||||
/// normally without producing a matching element, this publisher emits `false` and
|
||||
/// finishes.
|
||||
///
|
||||
/// In the example below, the `contains(_:)` operator emits `true` the first time it
|
||||
/// receives the value `5` from the `numbers.publisher`, and then finishes normally.
|
||||
///
|
||||
/// let numbers = [-1, 5, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .contains(5)
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "true"
|
||||
///
|
||||
/// - Parameter output: An element to match against.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
@@ -27,12 +39,27 @@ extension Publisher {
|
||||
/// Publishes a Boolean value upon receiving an element that satisfies the predicate
|
||||
/// closure.
|
||||
///
|
||||
/// This operator consumes elements produced from the upstream publisher until
|
||||
/// the upstream publisher produces a matching element.
|
||||
/// Use `contains(where:)` to find the first element in an upstream that satisfies
|
||||
/// the closure you provide. This operator consumes elements produced from
|
||||
/// the upstream publisher until the upstream publisher produces a matching element.
|
||||
///
|
||||
/// This operator is useful when the upstream publisher produces elements that don’t
|
||||
/// conform to `Equatable`.
|
||||
///
|
||||
/// In the example below, the `contains(where:)` operator tests elements against
|
||||
/// the supplied closure and emits `true` for the first elements that’s greater than
|
||||
/// `4`, and then finishes normally.
|
||||
///
|
||||
/// let numbers = [-1, 0, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .contains {$0 > 4}
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "true"
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether the element satisfies the closure’s
|
||||
/// comparison logic.
|
||||
/// returns a Boolean value that indicates whether the element satisfies
|
||||
/// the closure’s comparison logic.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func contains(
|
||||
@@ -41,16 +68,47 @@ extension Publisher {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes a Boolean value upon receiving an element that satisfies
|
||||
/// the throwing predicate closure.
|
||||
/// Publishes a Boolean value upon receiving an element that satisfies the throwing
|
||||
/// predicate closure.
|
||||
///
|
||||
/// Use `tryContains(where:)` to find the first element in an upstream that satisfies
|
||||
/// the error-throwing closure you provide.
|
||||
///
|
||||
/// This operator consumes elements produced from the upstream publisher until
|
||||
/// the upstream publisher produces a matching element. If the closure throws,
|
||||
/// the stream fails with an error.
|
||||
/// the upstream publisher either:
|
||||
///
|
||||
/// - Produces a matching element, after which it emits `true` and the publisher
|
||||
/// finishes normally.
|
||||
/// - Emits `false` if no matching element is found and the publisher finishes
|
||||
/// normally.
|
||||
///
|
||||
/// If the predicate throws an error, the publisher fails, passing the error to its
|
||||
/// downstream.
|
||||
///
|
||||
/// In the example below, the `tryContains(where:)` operator tests values to find
|
||||
/// an element less than `10`; when the closure finds an odd number, like `3`,
|
||||
/// the publisher terminates with an `IllegalValueError`.
|
||||
///
|
||||
/// struct IllegalValueError: Error {}
|
||||
///
|
||||
/// let numbers = [3, 2, 10, 5, 0, 9]
|
||||
/// numbers.publisher
|
||||
/// .tryContains {
|
||||
/// if ($0 % 2 != 0) {
|
||||
/// throw IllegalValueError()
|
||||
/// }
|
||||
/// return $0 < 10
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "completion: failure(IllegalValueError())"
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether the element satisfies the closure’s
|
||||
/// comparison logic.
|
||||
/// returns a Boolean value that indicates whether the element satisfies
|
||||
/// the closure’s comparison logic.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func tryContains(
|
||||
|
||||
@@ -9,8 +9,18 @@ extension Publisher {
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// Use `count(`` to determine the number of elements received from the upstream
|
||||
/// publisher before it completes:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .count()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "11"
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self> {
|
||||
return Publishers.Count(upstream: self)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,57 @@ 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.
|
||||
/// Use the `debounce(for:scheduler:options:)` operator to control the number of
|
||||
/// values and time between delivery of values from the upstream publisher. This
|
||||
/// operator is useful to process bursty or high-volume event streams where you need
|
||||
/// to reduce the number of values delivered to the downstream to a rate you specify.
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes elements on a schedule defined
|
||||
/// by the `bounces` array. The array is composed of tuples representing a value sent
|
||||
/// by the `PassthroughSubject`, and a `TimeInterval` ranging from one-quarter second
|
||||
/// up to 2 seconds that drives a delivery timer. As the queue builds, elements
|
||||
/// arriving faster than one-half second `debounceInterval` are discarded, while
|
||||
/// elements arriving at a rate slower than `debounceInterval` are passed through to
|
||||
/// the `sink(receiveValue:)` operator.
|
||||
///
|
||||
/// let bounces:[(Int,TimeInterval)] = [
|
||||
/// (0, 0),
|
||||
/// (1, 0.25), // 0.25s interval since last index
|
||||
/// (2, 1), // 0.75s interval since last index
|
||||
/// (3, 1.25), // 0.25s interval since last index
|
||||
/// (4, 1.5), // 0.25s interval since last index
|
||||
/// (5, 2) // 0.5s interval since last index
|
||||
/// ]
|
||||
///
|
||||
/// let subject = PassthroughSubject<Int, Never>()
|
||||
/// cancellable = subject
|
||||
/// .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
|
||||
/// .sink { index in
|
||||
/// print ("Received index \(index)")
|
||||
/// }
|
||||
///
|
||||
/// for bounce in bounces {
|
||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
|
||||
/// subject.send(bounce.0)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Received index 1
|
||||
/// // Received index 4
|
||||
/// // Received index 5
|
||||
///
|
||||
/// // Here is the event flow shown from the perspective of time, showing value
|
||||
/// // delivery through the `debounce()` operator:
|
||||
///
|
||||
/// // Time 0: Send index 0.
|
||||
/// // Time 0.25: Send index 1. Index 0 was waiting and is discarded.
|
||||
/// // Time 0.75: Debounce period ends, publish index 1.
|
||||
/// // Time 1: Send index 2.
|
||||
/// // Time 1.25: Send index 3. Index 2 was waiting and is discarded.
|
||||
/// // Time 1.5: Send index 4. Index 3 was waiting and is discarded.
|
||||
/// // Time 2: Debounce period ends, publish index 4. Also, send index 5.
|
||||
/// // Time 2.5: Debounce period ends, publish index 5.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - dueTime: The time the publisher should wait before publishing an element.
|
||||
@@ -92,6 +139,17 @@ extension Publishers.Debounce {
|
||||
|
||||
private typealias Generation = UInt64
|
||||
|
||||
private enum CancellerState {
|
||||
case pending
|
||||
case active(Cancellable)
|
||||
|
||||
fileprivate func cancel() {
|
||||
if case let .active(cancellable) = self {
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
@@ -106,7 +164,7 @@ extension Publishers.Debounce {
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var currentCanceller: Cancellable?
|
||||
private var currentCancellers = [Generation : CancellerState]()
|
||||
|
||||
private var currentValue: Output?
|
||||
|
||||
@@ -146,7 +204,6 @@ extension Publishers.Debounce {
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
precondition(!state.isAwaitingSubscription)
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
@@ -155,32 +212,38 @@ extension Publishers.Debounce {
|
||||
let generation = currentGeneration
|
||||
currentValue = input
|
||||
let due = scheduler.now.advanced(by: dueTime)
|
||||
let previousCancellers = self.currentCancellers
|
||||
currentCancellers.removeAll()
|
||||
currentCancellers[generation] = .pending
|
||||
lock.unlock()
|
||||
let newCanceller = scheduler.schedule(after: due,
|
||||
interval: dueTime,
|
||||
tolerance: scheduler.minimumTolerance,
|
||||
options: options) { [weak self] in
|
||||
self?.due(generation: generation)
|
||||
options: options) {
|
||||
self.due(generation: generation)
|
||||
}
|
||||
lock.lock()
|
||||
let canceller = currentCanceller
|
||||
currentCanceller = newCanceller
|
||||
currentCancellers[generation] = .active(newCanceller)
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
precondition(!state.isAwaitingSubscription)
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let canceller = currentCanceller
|
||||
let previousCancellers = currentCancellers
|
||||
currentCancellers.removeAll()
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
}
|
||||
scheduler.schedule {
|
||||
self.downstreamLock.lock()
|
||||
self.downstream.receive(completion: completion)
|
||||
@@ -190,7 +253,6 @@ extension Publishers.Debounce {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
precondition(!state.isAwaitingSubscription)
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
@@ -206,7 +268,12 @@ extension Publishers.Debounce {
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let previousCancellers = currentCancellers
|
||||
currentCancellers.removeAll()
|
||||
lock.unlock()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
}
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
@@ -233,18 +300,23 @@ extension Publishers.Debounce {
|
||||
// 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
|
||||
let canceller = currentCancellers[generation]
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
let hasAnyDemand = downstreamDemand > 0
|
||||
guard let canceller = currentCancellers[generation] else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
currentCancellers[generation] = nil
|
||||
|
||||
let hasAnyDemand = downstreamDemand != .none
|
||||
if hasAnyDemand {
|
||||
downstreamDemand -= 1
|
||||
}
|
||||
|
||||
let canceller = currentCanceller!
|
||||
lock.unlock()
|
||||
canceller.cancel()
|
||||
|
||||
|
||||
@@ -7,8 +7,52 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Delays delivery of all output to the downstream receiver by a specified amount
|
||||
/// of time on a particular scheduler.
|
||||
/// Delays delivery of all output to the downstream receiver by a specified amount of
|
||||
/// time on a particular scheduler.
|
||||
///
|
||||
/// Use `delay(for:tolerance:scheduler:options:)` when you need to delay the delivery
|
||||
/// of elements to a downstream by a specified amount of time.
|
||||
///
|
||||
/// In this example, a `Timer` publishes an event every second.
|
||||
/// The `delay(for:tolerance:scheduler:options:)` operator holds the delivery of
|
||||
/// the initial element for 3 seconds (±0.5 seconds), after which each element is
|
||||
/// delivered to the downstream on the main run loop after the specified delay:
|
||||
///
|
||||
/// let df = DateFormatter()
|
||||
/// df.dateStyle = .none
|
||||
/// df.timeStyle = .long
|
||||
/// cancellable = Timer.publish(every: 1.0, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .handleEvents(receiveOutput: { date in
|
||||
/// print ("Sending Timestamp \'\(df.string(from: date))\' to delay()")
|
||||
/// })
|
||||
/// .delay(for: .seconds(3), scheduler: RunLoop.main, options: .none)
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)", terminator: "\n") },
|
||||
/// receiveValue: { value in
|
||||
/// let now = Date()
|
||||
/// print("""
|
||||
/// At \(df.string(from: now)) received Timestamp \
|
||||
/// \'\(df.string(from: value))\' \
|
||||
/// sent: \(String(format: "%.2f", now.timeIntervalSince(value)))
|
||||
/// secs ago
|
||||
/// """)
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Sending Timestamp '5:02:33 PM PDT' to delay()
|
||||
/// // Sending Timestamp '5:02:34 PM PDT' to delay()
|
||||
/// // Sending Timestamp '5:02:35 PM PDT' to delay()
|
||||
/// // Sending Timestamp '5:02:36 PM PDT' to delay()
|
||||
/// // At 5:02:36 PM PDT received Timestamp '5:02:33 PM PDT' sent: 3.00
|
||||
/// // secs ago
|
||||
/// // Sending Timestamp '5:02:37 PM PDT' to delay()
|
||||
/// // At 5:02:37 PM PDT received Timestamp '5:02:34 PM PDT' sent: 3.00
|
||||
/// // secs ago
|
||||
/// // Sending Timestamp '5:02:38 PM PDT' to delay()
|
||||
/// // At 5:02:38 PM PDT received Timestamp '5:02:35 PM PDT' sent: 3.00
|
||||
/// // secs ago
|
||||
///
|
||||
/// The delay affects the delivery of elements and completion, but not of the original
|
||||
/// subscription.
|
||||
@@ -17,6 +61,7 @@ extension Publisher {
|
||||
/// - interval: The amount of time to delay.
|
||||
/// - tolerance: The allowed tolerance in firing delayed events.
|
||||
/// - scheduler: The scheduler to deliver the delayed events.
|
||||
/// - options: Options relevant to the scheduler’s behavior.
|
||||
/// - Returns: A publisher that delays delivery of elements and completion to
|
||||
/// the downstream receiver.
|
||||
public func delay<Context: Scheduler>(
|
||||
@@ -74,7 +119,12 @@ extension Publishers {
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
let inner = Inner(downstream: subscriber,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,25 +135,28 @@ extension Publishers.Delay {
|
||||
Subscription
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
fileprivate typealias Delay = Publishers.Delay<Upstream, Context>
|
||||
|
||||
private enum State {
|
||||
case ready(Delay, Downstream)
|
||||
case subscribed(Delay, Downstream, Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state: State
|
||||
private let downstream: Downstream
|
||||
private let interval: Context.SchedulerTimeType.Stride
|
||||
private let tolerance: Context.SchedulerTimeType.Stride
|
||||
private let scheduler: Context
|
||||
private let options: Context.SchedulerOptions?
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(_ publisher: Delay, downstream: Downstream) {
|
||||
state = .ready(publisher, downstream)
|
||||
fileprivate init(downstream: Downstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
tolerance: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.downstream = downstream
|
||||
self.interval = interval
|
||||
self.tolerance = tolerance
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -111,73 +164,82 @@ extension Publishers.Delay {
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
private func schedule(_ delay: Delay, work: @escaping () -> Void) {
|
||||
delay
|
||||
.scheduler
|
||||
.schedule(after: delay.scheduler.now.advanced(by: delay.interval),
|
||||
tolerance: delay.tolerance,
|
||||
options: delay.options,
|
||||
private func schedule(_ work: @escaping () -> Void) {
|
||||
scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: interval),
|
||||
tolerance: tolerance,
|
||||
options: options,
|
||||
work)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(delay, downstream) = state else {
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(delay, downstream, subscription)
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(delay, downstream, _) = state else {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
schedule(delay) {
|
||||
self.scheduledReceive(input, downstream: downstream)
|
||||
schedule {
|
||||
self.scheduledReceive(input)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
private func scheduledReceive(_ input: Upstream.Output, downstream: Downstream) {
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else {
|
||||
return
|
||||
}
|
||||
private func scheduledReceive(_ input: Input) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard state.subscription != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(newDemand)
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
if newDemand == .none { return }
|
||||
lock.lock()
|
||||
let subscription = state.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(newDemand)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(delay, downstream, _) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .pendingTerminal(subscription)
|
||||
lock.unlock()
|
||||
schedule {
|
||||
self.scheduledReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .pendingTerminal = state else {
|
||||
assertionFailure(
|
||||
"This branch should not be reachable! Please report a bug."
|
||||
)
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
schedule(delay) {
|
||||
self.scheduledReceive(completion: completion, downstream: downstream)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>,
|
||||
downstream: Downstream) {
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
@@ -185,7 +247,7 @@ extension Publishers.Delay {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -195,7 +257,7 @@ extension Publishers.Delay {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,8 +8,20 @@
|
||||
extension Publisher {
|
||||
/// Omits the specified number of elements before republishing subsequent elements.
|
||||
///
|
||||
/// - Parameter count: The number of elements to omit.
|
||||
/// - Returns: A publisher that does not republish the first `count` elements.
|
||||
/// Use `dropFirst(_:)` when you want to drop the first `n` elements from the upstream
|
||||
/// publisher, and republish the remaining elements.
|
||||
///
|
||||
/// The example below drops the first five elements from the stream:
|
||||
///
|
||||
/// let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .dropFirst(5)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "6 7 8 9 10 "
|
||||
///
|
||||
/// - Parameter count: The number of elements to omit. The default is `1`.
|
||||
/// - Returns: A publisher that doesn’t republish the first `count` elements.
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self> {
|
||||
return .init(upstream: self, count: count)
|
||||
}
|
||||
@@ -40,8 +52,8 @@ extension Publishers {
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, count: count)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,8 +70,6 @@ extension Publishers.Drop {
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
@@ -94,7 +104,7 @@ extension Publishers.Drop {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
let demandToRequestFromUpstream = pendingDemand + count
|
||||
lock.unlock()
|
||||
if demandToRequestFromUpstream > 0 {
|
||||
if demandToRequestFromUpstream != .none {
|
||||
subscription.request(demandToRequestFromUpstream)
|
||||
}
|
||||
}
|
||||
@@ -109,8 +119,9 @@ extension Publishers.Drop {
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
// Combine doesn't lock here!
|
||||
lock.lock()
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
@@ -127,9 +138,11 @@ extension Publishers.Drop {
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
// Combine doesn't lock here!
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
}
|
||||
|
||||
var description: String { return "Drop" }
|
||||
|
||||
@@ -10,20 +10,39 @@ extension Publisher {
|
||||
/// Ignores elements from the upstream publisher until it receives an element from
|
||||
/// a second publisher.
|
||||
///
|
||||
/// This publisher requests a single value from the upstream publisher, and it ignores
|
||||
/// (drops) all elements from that publisher until the upstream publisher produces
|
||||
/// a value. After the `other` publisher produces an element, this publisher cancels
|
||||
/// its subscription to the `other` publisher, and allows events from the `upstream`
|
||||
/// publisher to pass through.
|
||||
/// Use `drop(untilOutputFrom:)` to ignore elements from the upstream publisher until
|
||||
/// another, second, publisher delivers its first element.
|
||||
/// This publisher requests a single value from the second publisher, and it ignores
|
||||
/// (drops) all elements from the upstream publisher until the second publisher
|
||||
/// produces a value. After the second publisher produces an element,
|
||||
/// `drop(untilOutputFrom:)` cancels its subscription to the second publisher, and
|
||||
/// allows events from the upstream publisher to pass through.
|
||||
///
|
||||
/// After this publisher receives a subscription from the upstream publisher, it
|
||||
/// passes through backpressure requests from downstream to the upstream publisher.
|
||||
/// If the upstream publisher acts on those requests before the other publisher
|
||||
/// produces an item, this publisher drops the elements it receives from the upstream
|
||||
/// publisher.
|
||||
///
|
||||
/// In the example below, the `pub1` publisher defers publishing its elements until
|
||||
/// the `pub2` publisher delivers its first element:
|
||||
///
|
||||
/// let upstream = PassthroughSubject<Int, Never>()
|
||||
/// let second = PassthroughSubject<String, Never>()
|
||||
/// cancellable = upstream
|
||||
/// .drop(untilOutputFrom: second)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// upstream.send(1)
|
||||
/// upstream.send(2)
|
||||
/// second.send("A")
|
||||
/// upstream.send(3)
|
||||
/// upstream.send(4)
|
||||
/// // Prints "3 4"
|
||||
///
|
||||
/// - Parameter publisher: A publisher to monitor for its first emitted element.
|
||||
/// - Returns: A publisher that drops elements from the upstream publisher until the
|
||||
/// `other` publisher produces a value.
|
||||
/// - Returns: A publisher that drops elements from the upstream publisher until
|
||||
/// the `other` publisher produces a value.
|
||||
public func drop<Other: Publisher>(
|
||||
untilOutputFrom publisher: Other
|
||||
) -> Publishers.DropUntilOutput<Self, Other> where Failure == Other.Failure {
|
||||
|
||||
@@ -10,6 +10,21 @@ extension Publisher {
|
||||
/// Omits elements from the upstream publisher until a given closure returns false,
|
||||
/// before republishing all remaining elements.
|
||||
///
|
||||
/// Use `drop(while:)` to omit elements from an upstream publisher until the element
|
||||
/// received meets a condition you specify.
|
||||
///
|
||||
/// In the example below, the operator omits all elements in the stream until
|
||||
/// the first element arrives that’s a positive integer, after which the operator
|
||||
/// publishes all remaining elements:
|
||||
///
|
||||
/// let numbers = [-62, -1, 0, 10, 0, 22, 41, -1, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .drop { $0 <= 0 }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "10 0 22 41 -1 5"
|
||||
///
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and returns
|
||||
/// a Boolean value indicating whether to drop the element from the publisher’s
|
||||
/// output.
|
||||
@@ -24,7 +39,33 @@ extension Publisher {
|
||||
/// Omits elements from the upstream publisher until an error-throwing closure returns
|
||||
/// false, before republishing all remaining elements.
|
||||
///
|
||||
/// If the predicate closure throws, the publisher fails with an error.
|
||||
/// Use `Publisher/tryDrop(while:)` to omit elements from an upstream until
|
||||
/// an error-throwing closure you provide returns false, after which the remaining
|
||||
/// items in the stream are published. If the closure throws, no elements are emitted
|
||||
/// and the publisher fails with an error.
|
||||
///
|
||||
/// In the example below, elements are ignored until `-1` is encountered in the stream
|
||||
/// and the closure returns `false`. The publisher then republishes the remaining
|
||||
/// elements and finishes normally. Conversely, if the `guard` value in the closure
|
||||
/// had been encountered, the closure would throw and the publisher would fail with
|
||||
/// an error.
|
||||
///
|
||||
/// struct RangeError: Error {}
|
||||
/// var numbers = [1, 2, 3, 4, 5, 6, -1, 7, 8, 9, 10]
|
||||
/// let range: CountableClosedRange<Int> = (1...100)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryDrop {
|
||||
/// guard $0 != 0 else { throw RangeError() }
|
||||
/// return range.contains($0)
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "-1 7 8 9 10 completion: finished"
|
||||
/// // If instead numbers was [1, 2, 3, 4, 5, 6, 0, -1, 7, 8, 9, 10],
|
||||
/// // tryDrop(while:) would fail with a RangeError.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and returns
|
||||
/// a Boolean value indicating whether to drop the element from the publisher’s
|
||||
|
||||
@@ -11,16 +11,100 @@ instantiations = ['Encode', 'Decode']
|
||||
}%
|
||||
extension Publisher {
|
||||
|
||||
/// Encodes the output from upstream using a specified `TopLevelEncoder`.
|
||||
/// For example, use `JSONEncoder`.
|
||||
/// Encodes the output from upstream using a specified encoder.
|
||||
///
|
||||
/// Use `encode(encoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to encode an `Encodable` struct into `Data` that could be used to
|
||||
/// make a JSON string (or written to disk as a binary plist in the case of property
|
||||
/// lists).
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes an `Article`.
|
||||
/// The `encode(encoder:)` operator encodes the properties of the `Article` struct
|
||||
/// into a new JSON string according to the `Codable` protocol adopted by `Article`.
|
||||
/// The operator publishes the resulting JSON string to the downstream subscriber.
|
||||
/// If the encoding operation fails, which can happen in the case of complex
|
||||
/// properties that can’t be directly transformed into JSON, the stream terminates
|
||||
/// and the error is passed to the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Article, Never>()
|
||||
/// let cancellable = dataProvider
|
||||
/// .encode(encoder: JSONEncoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)") },
|
||||
/// receiveValue: { data in
|
||||
/// guard let stringRepresentation =
|
||||
/// String(data: data, encoding: .utf8) else { return }
|
||||
/// print("""
|
||||
/// Data received \(data) string representation: \
|
||||
/// \(stringRepresentation)
|
||||
/// """)
|
||||
/// })
|
||||
///
|
||||
/// dataProvider.send(Article(title: "My First Article",
|
||||
/// author: "Gita Kumar",
|
||||
/// pubDate: Date()))
|
||||
///
|
||||
/// // Prints: "Data received 86 bytes string representation:
|
||||
/// // {"title":"My First Article","author":"Gita Kumar"
|
||||
/// // "pubDate":606211803.279603}"
|
||||
///
|
||||
/// - Parameter encoder: An encoder that implements the `TopLevelEncoder` protocol.
|
||||
/// - Returns: A publisher that encodes received elements using a specified encoder,
|
||||
/// and publishes the resulting data.
|
||||
public func encode<Coder: TopLevelEncoder>(
|
||||
encoder: Coder
|
||||
) -> Publishers.Encode<Self, Coder> {
|
||||
return .init(upstream: self, encoder: encoder)
|
||||
}
|
||||
|
||||
/// Decodes the output from upstream using a specified `TopLevelDecoder`.
|
||||
/// For example, use `JSONDecoder`.
|
||||
/// Decodes the output from the upstream using a specified decoder.
|
||||
///
|
||||
/// Use `decode(type:decoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to decode data received from a `URLSession.DataTaskPublisher` or
|
||||
/// other data source using the `Decodable` protocol.
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes a JSON string. The JSON decoder
|
||||
/// parses the string, converting its fields according to the `Decodable` protocol
|
||||
/// implemented by `Article`, and successfully populating a new `Article`.
|
||||
/// The `Publishers.Decode` publisher then publishes the `Article` to the downstream.
|
||||
/// If a decoding operation fails, which happens in the case of missing or malformed
|
||||
/// data in the source JSON string, the stream terminates and passes the error to
|
||||
/// the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Data, Never>()
|
||||
/// cancellable = dataProvider
|
||||
/// .decode(type: Article.self, decoder: JSONDecoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)")},
|
||||
/// receiveValue: { print ("value: \($0)") })
|
||||
///
|
||||
/// dataProvider.send(Data("""
|
||||
/// {\"pubDate\":1574273638.575666, \
|
||||
/// \"title\" : \"My First Article\", \
|
||||
/// \"author\" : \"Gita Kumar\" }
|
||||
/// """.utf8))
|
||||
///
|
||||
/// // Prints:
|
||||
/// // ".sink() data received Article(title: "My First Article",
|
||||
/// // author: "Gita Kumar",
|
||||
/// // pubDate: 2050-11-20 18:13:58 +0000)"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - type: The encoded data to decode into a struct that conforms to
|
||||
/// the `Decodable` protocol.
|
||||
/// - decoder: A decoder that implements the `TopLevelDecoder` protocol.
|
||||
/// - Returns: A publisher that decodes a given type using a specified decoder and
|
||||
/// publishes the result.
|
||||
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
@@ -87,9 +171,6 @@ extension Publishers.${instantiation} {
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
@@ -98,6 +179,8 @@ extension Publishers.${instantiation} {
|
||||
|
||||
private let ${instantiation.lower()}: (Upstream.Output) throws -> Output
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
@@ -110,44 +193,72 @@ extension Publishers.${instantiation} {
|
||||
self.${instantiation.lower()} = ${instantiation.lower()}
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if finished { return .none }
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
do {
|
||||
return try downstream.receive(${instantiation.lower()}(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if finished { return }
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let subscription = self.subscription, !finished else { return }
|
||||
subscription.cancel()
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "${instantiation}" }
|
||||
|
||||
@@ -9,6 +9,20 @@ extension Publisher {
|
||||
|
||||
/// Republishes all elements that match a provided closure.
|
||||
///
|
||||
/// OpenCombine’s `filter(_:)` operator performs an operation similar to that of
|
||||
/// `filter(_:)` in the Swift Standard Library: it uses a closure to test each element
|
||||
/// to determine whether to republish the element to the downstream subscriber.
|
||||
///
|
||||
/// The following example, uses a filter operation that receives an `Int` and only
|
||||
/// republishes a value if it’s even.
|
||||
///
|
||||
/// let numbers: [Int] = [1, 2, 3, 4, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .filter { $0 % 2 == 0 }
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "2 4"
|
||||
///
|
||||
/// - Parameter isIncluded: A closure that takes one element and returns
|
||||
/// a Boolean value indicating whether to republish the element.
|
||||
/// - Returns: A publisher that republishes all elements that satisfy the closure.
|
||||
@@ -20,11 +34,34 @@ extension Publisher {
|
||||
|
||||
/// Republishes all elements that match a provided error-throwing closure.
|
||||
///
|
||||
/// Use `tryFilter(_:)` to filter elements evaluated in an error-throwing closure.
|
||||
/// If the `isIncluded` closure throws an error, the publisher fails with that error.
|
||||
///
|
||||
/// - Parameter isIncluded: A closure that takes one element and returns a
|
||||
/// Boolean value indicating whether to republish the element.
|
||||
/// - Returns: A publisher that republishes all elements that satisfy the closure.
|
||||
/// In the example below, `tryFilter(_:)` checks to see if the divisor provided by
|
||||
/// the publisher is zero, and throws a `DivisionByZeroError` and then terminates
|
||||
/// the publisher with the thrown error:
|
||||
///
|
||||
/// struct DivisionByZeroError: Error {}
|
||||
///
|
||||
/// let numbers: [Int] = [1, 2, 3, 4, 0, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryFilter {
|
||||
/// if $0 == 0 {
|
||||
/// throw DivisionByZeroError()
|
||||
/// } else {
|
||||
/// return $0 % 2 == 0
|
||||
/// }
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("\($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "2 4 failure(DivisionByZeroError())".
|
||||
///
|
||||
/// - Parameter isIncluded: A closure that takes one element and returns a Boolean
|
||||
/// value that indicated whether to republish the element or throws an error.
|
||||
/// - Returns: A publisher that republishes all elements that satisfy the closure.
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Self> {
|
||||
@@ -142,23 +179,48 @@ extension Publishers {
|
||||
}
|
||||
|
||||
extension Publishers.Filter {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
|
||||
return filter(newValue) ? .continue(newValue) : .continue(nil)
|
||||
private let downstream: Downstream
|
||||
private let filter: (Input) -> Bool
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(downstream: Downstream, filter: @escaping (Input) -> Bool) {
|
||||
self.downstream = downstream
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
override var description: String { return "Filter" }
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if filter(input) {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Filter" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +233,6 @@ extension Publishers.TryFilter {
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Upstream.Output?, Error> {
|
||||
|
||||
@@ -9,38 +9,89 @@ extension Publisher {
|
||||
|
||||
/// Publishes the first element of a stream, then finishes.
|
||||
///
|
||||
/// If this publisher doesn’t receive any elements, it finishes without publishing.
|
||||
/// Use `first()` to publish just the first element from an upstream publisher, then
|
||||
/// finish normally. The `first()` operator requests `Subscribers.Demand.unlimited`
|
||||
/// from its upstream as soon as downstream requests at least one element.
|
||||
/// If the upstream completes before `first()` receives any elements, it completes
|
||||
/// without emitting any values.
|
||||
///
|
||||
/// In this example, the `first()` publisher republishes the first element received
|
||||
/// from the sequence publisher, `-10`, then finishes normally.
|
||||
///
|
||||
/// let numbers = (-10...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .first()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Print: "-10"
|
||||
///
|
||||
/// - Returns: A publisher that only publishes the first element of a stream.
|
||||
public func first() -> Publishers.First<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
|
||||
/// Publishes the first element of a stream to
|
||||
/// satisfy a predicate closure, then finishes.
|
||||
/// Publishes the first element of a stream to satisfy a predicate closure, then
|
||||
/// finishes normally.
|
||||
///
|
||||
/// The publisher ignores all elements after the first.
|
||||
/// If this publisher doesn’t receive any elements,
|
||||
/// it finishes without publishing.
|
||||
/// - 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.
|
||||
/// Use `first(where:)` to republish only the first element of a stream that satisfies
|
||||
/// a closure you specify. The publisher ignores all elements after the first element
|
||||
/// that satisfies the closure and finishes normally.
|
||||
/// If this publisher doesn’t receive any elements, it finishes without publishing.
|
||||
///
|
||||
/// In the example below, the provided closure causes the `Publishers.FirstWhere`
|
||||
/// publisher to republish the first received element that’s greater than `0`,
|
||||
/// then finishes normally.
|
||||
///
|
||||
/// let numbers = (-10...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .first { $0 > 0 }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "1"
|
||||
///
|
||||
|
||||
/// - 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.
|
||||
public func first(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.FirstWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes the first element of a stream to satisfy a
|
||||
/// throwing predicate closure, then finishes.
|
||||
/// Publishes the first element of a stream to satisfy a throwing predicate closure,
|
||||
/// then finishes normally.
|
||||
///
|
||||
/// The publisher ignores all elements after the first. If this publisher
|
||||
/// doesn’t receive any elements, it finishes without publishing. If the
|
||||
/// predicate closure throws, the publisher fails with an error.
|
||||
/// - 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.
|
||||
/// Use `tryFirst(where:)` when you need to republish only the first element of
|
||||
/// a stream that satisfies an error-throwing closure you specify.
|
||||
/// The publisher ignores all elements after the first. If this publisher doesn’t
|
||||
/// receive any elements, it finishes without publishing. If the predicate closure
|
||||
/// throws an error, the publisher fails.
|
||||
///
|
||||
/// In the example below, a range publisher emits the first element in the range then
|
||||
/// finishes normally:
|
||||
///
|
||||
/// let numberRange: ClosedRange<Int> = (-1...50)
|
||||
/// numberRange.publisher
|
||||
/// .tryFirst {
|
||||
/// guard $0 < 99 else {throw RangeError()}
|
||||
/// return true
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)", terminator: " ") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "-1 completion: finished"
|
||||
/// // If instead the number range were ClosedRange<Int> = (100...200),
|
||||
/// // the tryFirst operator would terminate publishing with a RangeError.
|
||||
///
|
||||
|
||||
/// - 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.
|
||||
public func tryFirst(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFirstWhere<Self> {
|
||||
|
||||
@@ -5,16 +5,57 @@
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
/// Transforms all elements from an upstream publisher into a new or existing
|
||||
/// publisher.
|
||||
|
||||
/// Transforms all elements from an upstream publisher into a new publisher up to
|
||||
/// a maximum number of publishers you specify.
|
||||
///
|
||||
/// `flatMap` merges the output from all returned publishers into a single stream of
|
||||
/// output.
|
||||
/// OpenCombine‘s `flatMap(maxPublishers:_:)` operator performs a similar function
|
||||
/// to the `flatMap(_:)` operator in the Swift standard library, but turns
|
||||
/// the elements from one kind of publisher into a new publisher that is sent
|
||||
/// to subscribers. Use `flatMap(maxPublishers:_:)` when you want to create a new
|
||||
/// series of events for downstream subscribers based on the received value.
|
||||
/// The closure creates the new `Publishe`` based on the received value.
|
||||
/// The new `Publisher` can emit more than one event, and successful completion of
|
||||
/// the new `Publisher` does not complete the overall stream.
|
||||
/// Failure of the new `Publisher` will fail the overall stream.
|
||||
///
|
||||
/// In the example below, a `PassthroughSubject` publishes `WeatherStation` elements.
|
||||
/// The `flatMap(maxPublishers:_:)` receives each element, creates a `URL` from it,
|
||||
/// and produces a new `URLSession.DataTaskPublisher`, which will publish the data
|
||||
/// loaded from that `URL`.
|
||||
///
|
||||
/// public struct WeatherStation {
|
||||
/// public let stationID: String
|
||||
/// }
|
||||
///
|
||||
/// var weatherPublisher = PassthroughSubject<WeatherStation, URLError>()
|
||||
///
|
||||
/// cancellable = weatherPublisher
|
||||
/// .flatMap { station -> URLSession.DataTaskPublisher in
|
||||
/// let url = URL(string: """
|
||||
/// https://weatherapi.example.com/stations/\(station.stationID)\
|
||||
/// /observations/latest
|
||||
/// """)!
|
||||
/// return URLSession.shared.dataTaskPublisher(for: url)
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { completion in
|
||||
/// // Handle publisher completion (normal or error).
|
||||
/// },
|
||||
/// receiveValue: {
|
||||
/// // Process the received data.
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// weatherPublisher.send(WeatherStation(stationID: "KSFO")) // San Francisco, CA
|
||||
/// weatherPublisher.send(WeatherStation(stationID: "EGLC")) // London, UK
|
||||
/// weatherPublisher.send(WeatherStation(stationID: "ZBBB")) // Beijing, CN
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - maxPublishers: The maximum number of publishers produced by this method.
|
||||
/// - transform: A closure that takes an element as a parameter and returns a
|
||||
/// publisher that produces elements of that type.
|
||||
/// - maxPublishers: Specifies the maximum number of concurrent publisher
|
||||
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
|
||||
/// - transform: A closure that takes an element as a parameter and returns
|
||||
/// a publisher that produces elements of that type.
|
||||
/// - Returns: A publisher that transforms elements from an upstream publisher into
|
||||
/// a publisher of that element’s type.
|
||||
public func flatMap<Result, Child: Publisher>(
|
||||
@@ -28,7 +69,72 @@ extension Publisher {
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Transforms all elements from an upstream publisher into a new publisher up to
|
||||
/// a maximum number of publishers you specify.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - maxPublishers: Specifies the maximum number of concurrent publisher
|
||||
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
|
||||
/// - transform: A closure that takes an element as a parameter and returns
|
||||
/// a publisher that produces elements of that type.
|
||||
/// - Returns: A publisher that transforms elements from an upstream publisher into
|
||||
/// a publisher of that element’s type.
|
||||
public func flatMap<Child: Publisher>(
|
||||
maxPublishers: Subscribers.Demand = .unlimited,
|
||||
_ transform: @escaping (Output) -> Child
|
||||
) -> Publishers.FlatMap<Child, Publishers.SetFailureType<Self, Child.Failure>> {
|
||||
return setFailureType(to: Child.Failure.self)
|
||||
.flatMap(maxPublishers: maxPublishers, transform)
|
||||
}
|
||||
|
||||
/// Transforms all elements from an upstream publisher into a new publisher up to
|
||||
/// a maximum number of publishers you specify.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - maxPublishers: Specifies the maximum number of concurrent publisher
|
||||
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
|
||||
/// - transform: A closure that takes an element as a parameter and returns
|
||||
/// a publisher that produces elements of that type.
|
||||
/// - Returns: A publisher that transforms elements from an upstream publisher
|
||||
/// into a publisher of that element’s type.
|
||||
public func flatMap<Child: Publisher>(
|
||||
maxPublishers: Subscribers.Demand = .unlimited,
|
||||
_ transform: @escaping (Output) -> Child
|
||||
) -> Publishers.FlatMap<Child, Self> where Child.Failure == Never {
|
||||
return .init(upstream: self, maxPublishers: maxPublishers, transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms all elements from an upstream publisher into a new publisher up to
|
||||
/// a maximum number of publishers you specify.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - maxPublishers: Specifies the maximum number of concurrent publisher
|
||||
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
|
||||
/// - transform: A closure that takes an element as a parameter and returns
|
||||
/// a publisher that produces elements of that type.
|
||||
/// - Returns: A publisher that transforms elements from an upstream publisher into
|
||||
/// a publisher of that element’s type.
|
||||
public func flatMap<Child: Publisher>(
|
||||
maxPublishers: Subscribers.Demand = .unlimited,
|
||||
_ transform: @escaping (Output) -> Child
|
||||
) -> Publishers.FlatMap<Publishers.SetFailureType<Child, Failure>, Self>
|
||||
where Child.Failure == Never
|
||||
{
|
||||
return flatMap(maxPublishers: maxPublishers) {
|
||||
transform($0).setFailureType(to: Failure.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that transforms elements from an upstream publisher into a new
|
||||
/// publisher.
|
||||
public struct FlatMap<Child: Publisher, Upstream: Publisher>: Publisher
|
||||
where Child.Failure == Upstream.Failure
|
||||
{
|
||||
@@ -51,17 +157,17 @@ extension Publishers {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
let outer = Outer(downstream: subscriber,
|
||||
maxPublishers: maxPublishers,
|
||||
map: transform)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: outer)
|
||||
upstream.subscribe(outer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
private final class Outer<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
@@ -137,7 +243,7 @@ extension Publishers.FlatMap {
|
||||
subscription.request(maxPublishers)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
let cancelledOrCompleted = self.cancelledOrCompleted
|
||||
lock.unlock()
|
||||
@@ -154,9 +260,9 @@ extension Publishers.FlatMap {
|
||||
return .none
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
|
||||
outerSubscription = nil
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
outerSubscription = nil
|
||||
outerFinished = true
|
||||
switch completion {
|
||||
case .finished:
|
||||
@@ -166,6 +272,8 @@ extension Publishers.FlatMap {
|
||||
let wasAlreadyCancelledOrCompleted = cancelledOrCompleted
|
||||
cancelledOrCompleted = true
|
||||
for (_, subscription) in subscriptions {
|
||||
// Cancelling subscriptions with the lock acquired. Not good,
|
||||
// but that's what Combine does. This code path is tested.
|
||||
subscription.cancel()
|
||||
}
|
||||
subscriptions = [:]
|
||||
@@ -248,16 +356,21 @@ extension Publishers.FlatMap {
|
||||
|
||||
fileprivate func cancel() {
|
||||
lock.lock()
|
||||
if cancelledOrCompleted {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelledOrCompleted = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = [:]
|
||||
let outerSubscription = self.outerSubscription
|
||||
self.outerSubscription = nil
|
||||
lock.unlock()
|
||||
for (_, subscription) in subscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
// Combine doesn't acquire the lock here. Weird.
|
||||
// Combine doesn't acquire outerLock here. Weird.
|
||||
outerSubscription?.cancel()
|
||||
outerSubscription = nil
|
||||
}
|
||||
|
||||
// MARK: - Reflection
|
||||
@@ -365,9 +478,9 @@ extension Publishers.FlatMap {
|
||||
private func releaseLockThenSendCompletionDownstreamIfNeeded(
|
||||
outerFinished: Bool
|
||||
) -> Bool {
|
||||
#if DEBUG
|
||||
#if DEBUG
|
||||
lock.assertOwner() // Sanity check
|
||||
#endif
|
||||
#endif
|
||||
if !cancelledOrCompleted && outerFinished && buffer.isEmpty &&
|
||||
subscriptions.count + pendingSubscriptions == 0 {
|
||||
cancelledOrCompleted = true
|
||||
@@ -389,10 +502,10 @@ extension Publishers.FlatMap {
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible {
|
||||
private let index: SubscriptionIndex
|
||||
private let inner: Inner
|
||||
private let inner: Outer
|
||||
fileprivate let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(index: SubscriptionIndex, inner: Inner) {
|
||||
fileprivate init(index: SubscriptionIndex, inner: Outer) {
|
||||
self.index = index
|
||||
self.inner = inner
|
||||
}
|
||||
|
||||
@@ -9,6 +9,35 @@ extension Publisher {
|
||||
|
||||
/// Performs the specified closures when publisher events occur.
|
||||
///
|
||||
/// Use `handleEvents` when you want to examine elements as they progress through
|
||||
/// the stages of the publisher’s lifecycle.
|
||||
///
|
||||
/// In the example below, a publisher of integers shows the effect of printing
|
||||
/// debugging information at each stage of the element-processing lifecycle:
|
||||
///
|
||||
/// let integers = (0...2)
|
||||
/// cancellable = integers.publisher
|
||||
/// .handleEvents(receiveSubscription: { subs in
|
||||
/// print("Subscription: \(subs.combineIdentifier)")
|
||||
/// }, receiveOutput: { anInt in
|
||||
/// print("in output handler, received \(anInt)")
|
||||
/// }, receiveCompletion: { _ in
|
||||
/// print("in completion handler")
|
||||
/// }, receiveCancel: {
|
||||
/// print("received cancel")
|
||||
/// }, receiveRequest: { (demand) in
|
||||
/// print("received demand: \(demand.description)")
|
||||
/// })
|
||||
/// .sink { _ in return }
|
||||
///
|
||||
/// // Prints:
|
||||
/// // received demand: unlimited
|
||||
/// // Subscription: 0x7f81284734c0
|
||||
/// // in output handler, received 0
|
||||
/// // in output handler, received 1
|
||||
/// // in output handler, received 2
|
||||
/// // in completion handler
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveSubscription: A closure that executes when the publisher receives
|
||||
/// the subscription from the upstream publisher. Defaults to `nil`.
|
||||
@@ -91,7 +120,6 @@ extension Publishers {
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(self, downstream: subscriber)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
@@ -110,13 +138,21 @@ extension Publishers.HandleEvents {
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var events: Publishers.HandleEvents<Upstream>?
|
||||
public var receiveSubscription: ((Subscription) -> Void)?
|
||||
public var receiveOutput: ((Upstream.Output) -> Void)?
|
||||
public var receiveCompletion:
|
||||
((Subscribers.Completion<Upstream.Failure>) -> Void)?
|
||||
public var receiveCancel: (() -> Void)?
|
||||
public var receiveRequest: ((Subscribers.Demand) -> Void)?
|
||||
private let downstream: Downstream
|
||||
|
||||
init(_ events: Publishers.HandleEvents<Upstream>, downstream: Downstream) {
|
||||
self.events = events
|
||||
self.receiveSubscription = events.receiveSubscription
|
||||
self.receiveOutput = events.receiveOutput
|
||||
self.receiveCompletion = events.receiveCompletion
|
||||
self.receiveCancel = events.receiveCancel
|
||||
self.receiveRequest = events.receiveRequest
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
@@ -125,61 +161,83 @@ extension Publishers.HandleEvents {
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
events?.receiveSubscription?(subscription)
|
||||
lock.lock()
|
||||
if let receiveSubscription = self.receiveSubscription {
|
||||
lock.unlock()
|
||||
receiveSubscription(subscription)
|
||||
lock.lock()
|
||||
}
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
self.pendingDemand = .none
|
||||
lock.unlock()
|
||||
if pendingDemand > 0 {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
events?.receiveOutput?(input)
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if let receiveOutput = self.receiveOutput {
|
||||
lock.unlock()
|
||||
receiveOutput(input)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
let newDemand = downstream.receive(input)
|
||||
if newDemand > 0 {
|
||||
events?.receiveRequest?(newDemand)
|
||||
if newDemand == .none {
|
||||
return newDemand
|
||||
}
|
||||
lock.lock()
|
||||
if let receiveRequest = self.receiveRequest {
|
||||
lock.unlock()
|
||||
receiveRequest(newDemand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
events?.receiveCompletion?(completion)
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
events = nil
|
||||
status = .terminal
|
||||
if let receiveCompletion = self.receiveCompletion {
|
||||
lock.unlock()
|
||||
receiveCompletion(completion)
|
||||
lock.lock()
|
||||
}
|
||||
lockedTerminate()
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
events?.receiveRequest?(demand)
|
||||
lock.lock()
|
||||
if case let .subscribed(subscription) = status {
|
||||
if let receiveRequest = self.receiveRequest {
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
return
|
||||
receiveRequest(demand)
|
||||
lock.lock()
|
||||
}
|
||||
pendingDemand += demand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
events?.receiveCancel?()
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
events = nil
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
if let receiveCancel = self.receiveCancel {
|
||||
lock.unlock()
|
||||
receiveCancel()
|
||||
lock.lock()
|
||||
}
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lockedTerminate()
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
@@ -189,5 +247,14 @@ extension Publishers.HandleEvents {
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
private func lockedTerminate() {
|
||||
receiveSubscription = nil
|
||||
receiveOutput = nil
|
||||
receiveCompletion = nil
|
||||
receiveCancel = nil
|
||||
receiveRequest = nil
|
||||
status = .terminal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,33 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ignores all upstream elements, but passes along a completion
|
||||
/// state (finished or failed).
|
||||
/// Ingores all upstream elements, but passes along a completion state (finished or
|
||||
/// failed).
|
||||
///
|
||||
/// Use the `ignoreOutput(`` operator to determine if a publisher is able to complete
|
||||
/// successfully or would fail.
|
||||
///
|
||||
/// In the example below, the array publisher (`numbers`) delivers the first five of
|
||||
/// its elements successfully, as indicated by the `ignoreOutput()` operator.
|
||||
/// The operator consumes, but doesn’t republish the elements downstream. However,
|
||||
/// the sixth element, `0`, causes the error throwing closure to catch
|
||||
/// a `NoZeroValuesAllowedError` that terminates the stream.
|
||||
///
|
||||
/// struct NoZeroValuesAllowedError: Error {}
|
||||
/// let numbers = [1, 2, 3, 4, 5, 0, 6, 7, 8, 9]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryFilter({ anInt in
|
||||
/// guard anInt != 0 else { throw NoZeroValuesAllowedError() }
|
||||
/// return anInt < 20
|
||||
/// })
|
||||
/// .ignoreOutput()
|
||||
/// .sink(receiveCompletion: {print("completion: \($0)")},
|
||||
/// receiveValue: {print("value \($0)")})
|
||||
///
|
||||
/// // Prints: "completion: failure(NoZeroValuesAllowedError())"
|
||||
///
|
||||
/// The output type of this publisher is `Never`.
|
||||
///
|
||||
/// - Returns: A publisher that ignores all upstream elements.
|
||||
public func ignoreOutput() -> Publishers.IgnoreOutput<Self> {
|
||||
return .init(upstream: self)
|
||||
@@ -41,44 +64,27 @@ extension Publishers {
|
||||
}
|
||||
|
||||
extension Publishers.IgnoreOutput {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Never, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
downstream.receive(subscription: subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
@@ -87,42 +93,13 @@ extension Publishers.IgnoreOutput {
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
// ignore and requests from downstream since we'll never send
|
||||
// any values
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "IgnoreOutput" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("status", status)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
@@ -7,33 +7,82 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Only publishes the last element of a stream, after the stream finishes.
|
||||
/// Publishes the last element of a stream, after the stream finishes.
|
||||
///
|
||||
/// Use `last()` when you need to emit only the last element from an upstream
|
||||
/// publisher.
|
||||
///
|
||||
/// In the example below, the range publisher only emits the last element from
|
||||
/// the sequence publisher, `10`, then finishes normally.
|
||||
///
|
||||
/// let numbers = (-10...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .last()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "10"
|
||||
///
|
||||
/// - Returns: A publisher that only publishes the last element of a stream.
|
||||
public func last() -> Publishers.Last<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
|
||||
/// Only publishes the last element of a stream that satisfies a predicate closure,
|
||||
/// after the stream finishes.
|
||||
/// Publishes the last element of a stream that satisfies a predicate closure,
|
||||
/// after upstream finishes.
|
||||
///
|
||||
/// Use `last(where:)` when you need to republish only the last element of a stream
|
||||
/// that satisfies a closure you specify.
|
||||
///
|
||||
/// In the example below, a range publisher emits the last element that satisfies
|
||||
/// the closure’s criteria, then finishes normally:
|
||||
///
|
||||
/// let numbers = (-10...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .last { $0 < 6 }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "5"
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the last element satisfying
|
||||
/// the given predicate.
|
||||
/// returns a Boolean value that indicates whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the last element satisfying the given
|
||||
/// predicate.
|
||||
public func last(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.LastWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Only publishes the last element of a stream that satisfies an error-throwing
|
||||
/// predicate closure, after the stream finishes.
|
||||
/// Publishes the last element of a stream that satisfies an error-throwing predicate
|
||||
/// closure, after the stream finishes.
|
||||
///
|
||||
/// Use `tryLast(where:)` when you need to republish the last element that satisfies
|
||||
/// an error-throwing closure you specify. If the predicate closure throws an error,
|
||||
/// the publisher fails.
|
||||
///
|
||||
/// In the example below, a publisher emits the last element that satisfies
|
||||
/// the error-throwing closure, then finishes normally:
|
||||
///
|
||||
/// struct RangeError: Error {}
|
||||
///
|
||||
/// let numbers = [-62, 1, 6, 10, 9, 22, 41, -1, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryLast {
|
||||
/// guard 0 != 0 else {throw RangeError()}
|
||||
/// return true
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)", terminator: " ") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
/// // Prints: "5 completion: finished"
|
||||
/// // If instead the numbers array had contained a `0`, the `tryLast` operator
|
||||
/// // would terminate publishing with a RangeError."
|
||||
///
|
||||
/// If the predicate closure throws, the publisher fails with the thrown error.
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the last element satisfying
|
||||
/// the given predicate.
|
||||
/// returns a Boolean value that indicates whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the last element satisfying the given
|
||||
/// predicate.
|
||||
public func tryLast(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryLastWhere<Self> {
|
||||
|
||||
@@ -9,6 +9,34 @@ extension Publisher where Failure == Never {
|
||||
|
||||
/// Creates a connectable wrapper around the publisher.
|
||||
///
|
||||
/// In the following example, `makeConnectable()` wraps its upstream publisher
|
||||
/// (an instance of `Publishers.Share`) with a `ConnectablePublisher`. Without this,
|
||||
/// the first sink subscriber would receive all the elements from the sequence
|
||||
/// publisher and cause it to complete before the second subscriber attaches.
|
||||
/// By making the publisher connectable, the publisher doesn’t produce any elements
|
||||
/// until after the `connect()` call.
|
||||
///
|
||||
/// let subject = Just<String>("Sent")
|
||||
/// let pub = subject
|
||||
/// .share()
|
||||
/// .makeConnectable()
|
||||
/// cancellable1 = pub.sink { print ("Stream 1 received: \($0)") }
|
||||
///
|
||||
/// // For example purposes, use DispatchQueue to add a second subscriber
|
||||
/// // a second later, and then connect to the publisher a second after that.
|
||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
/// self.cancellable2 = pub.sink { print ("Stream 2 received: \($0)") }
|
||||
/// }
|
||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
/// self.connectable = pub.connect()
|
||||
/// }
|
||||
/// // Prints:
|
||||
/// // Stream 2 received: Sent
|
||||
/// // Stream 1 received: Sent
|
||||
///
|
||||
/// > Note: The `connect()` operator returns a `Cancellable` instance that you must
|
||||
/// retain. You can also use this instance to cancel publishing.
|
||||
///
|
||||
/// - Returns: A `ConnectablePublisher` wrapping this publisher.
|
||||
public func makeConnectable() -> Publishers.MakeConnectable<Self> {
|
||||
return .init(upstream: self)
|
||||
@@ -17,6 +45,15 @@ extension Publisher where Failure == Never {
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that provides explicit connectability to another publisher.
|
||||
///
|
||||
/// `Publishers.MakeConnectable` is a `ConnectablePublisher`, which allows you to
|
||||
/// perform configuration before publishing any elements. Call `connect()` on this
|
||||
/// publisher when you want to attach to its upstream publisher and start producing
|
||||
/// elements.
|
||||
///
|
||||
/// Use the `makeConnectable()` operator to wrap an upstream publisher with
|
||||
/// an instance of this publisher.
|
||||
public struct MakeConnectable<Upstream: Publisher>: ConnectablePublisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
@@ -25,6 +62,9 @@ extension Publishers {
|
||||
|
||||
private let inner: Multicast<Upstream, PassthroughSubject<Output, Failure>>
|
||||
|
||||
/// Creates a connectable publisher, attached to the provide upstream publisher.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which to receive elements.
|
||||
public init(upstream: Upstream) {
|
||||
inner = upstream.multicast(subject: .init())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,30 @@ extension Publisher {
|
||||
|
||||
/// Transforms all elements from the upstream publisher with a provided closure.
|
||||
///
|
||||
/// OpenCombine’s `map(_:)` operator performs a function similar to that of `map(_:)`
|
||||
/// in the Swift standard library: it uses a closure to transform each element it
|
||||
/// receives from the upstream publisher. You use `map(_:)` to transform from one kind
|
||||
/// of element to another.
|
||||
///
|
||||
/// The following example uses an array of numbers as the source for a collection
|
||||
/// based publisher. A `map(_:)` operator consumes each integer from the publisher and
|
||||
/// uses a dictionary to transform it from its Arabic numeral to a Roman equivalent,
|
||||
/// as a `String`.
|
||||
/// If the `map(_:)`’s closure fails to look up a Roman numeral, it returns the string
|
||||
/// `(unknown)`.
|
||||
///
|
||||
/// let numbers = [5, 4, 3, 2, 1, 0]
|
||||
/// let romanNumeralDict: [Int : String] =
|
||||
/// [1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .map { romanNumeralDict[$0] ?? "(unknown)" }
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "V IV III II I (unknown)"
|
||||
///
|
||||
/// If your closure can throw an error, use OpenCombine’s `tryMap(_:)` operator
|
||||
/// instead.
|
||||
///
|
||||
/// - Parameter transform: A closure that takes one element as its parameter and
|
||||
/// returns a new element.
|
||||
/// - Returns: A publisher that uses the provided closure to map elements from
|
||||
@@ -19,14 +43,47 @@ extension Publisher {
|
||||
return Publishers.Map(upstream: self, transform: transform)
|
||||
}
|
||||
|
||||
/// Transforms all elements from the upstream publisher with a provided
|
||||
/// error-throwing closure.
|
||||
/// Transforms all elements from the upstream publisher with a provided error-throwing
|
||||
/// closure.
|
||||
///
|
||||
/// If the `transform` closure throws an error, the publisher fails with the thrown
|
||||
/// error.
|
||||
/// OpenCombine’s `tryMap(_:)` operator performs a function similar to that of
|
||||
/// `map(_:)` in the Swift standard library: it uses a closure to transform each
|
||||
/// element it receives from the upstream publisher. You use `tryMap(_:)` to transform
|
||||
/// from one kind of element to another, and to terminate publishing when the map’s
|
||||
/// closure throws an error.
|
||||
///
|
||||
/// The following example uses an array of numbers as the source for a collection
|
||||
/// based publisher. A `tryMap(_:)` operator consumes each integer from the publisher
|
||||
/// and uses a dictionary to transform it from its Arabic numeral to a Roman
|
||||
/// equivalent, as a `String`.
|
||||
/// If the `tryMap(_:)`’s closure fails to look up a Roman numeral, it throws
|
||||
/// an error. The `tryMap(_:)` operator catches this error and terminates publishing,
|
||||
/// sending a `Subscribers.Completion.failure(_:)` that wraps the error.
|
||||
///
|
||||
/// struct ParseError: Error {}
|
||||
/// func romanNumeral(from:Int) throws -> String {
|
||||
/// let romanNumeralDict: [Int : String] =
|
||||
/// [1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"]
|
||||
/// guard let numeral = romanNumeralDict[from] else {
|
||||
/// throw ParseError()
|
||||
/// }
|
||||
/// return numeral
|
||||
/// }
|
||||
/// let numbers = [5, 4, 3, 2, 1, 0]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMap { try romanNumeral(from: $0) }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "V IV III II I completion: failure(ParseError())"
|
||||
///
|
||||
/// If your closure doesn’t throw, use `map(_:)` instead.
|
||||
///
|
||||
/// - Parameter transform: A closure that takes one element as its parameter and
|
||||
/// returns a new element.
|
||||
/// returns a new element. If the closure throws an error, the publisher fails with
|
||||
/// the thrown error.
|
||||
/// - Returns: A publisher that uses the provided closure to map elements from
|
||||
/// the upstream publisher to new elements that it then publishes.
|
||||
public func tryMap<Result>(
|
||||
@@ -34,6 +91,30 @@ extension Publisher {
|
||||
) -> Publishers.TryMap<Self, Result> {
|
||||
return Publishers.TryMap(upstream: self, transform: transform)
|
||||
}
|
||||
|
||||
/// Replaces `nil` elements in the stream with the provided element.
|
||||
///
|
||||
/// The `replaceNil(with:)` operator enables replacement of `nil` values in a stream
|
||||
/// with a substitute value. In the example below, a collection publisher contains
|
||||
/// a `nil` value. The `replaceNil(with:)` operator replaces this with `0.0`.
|
||||
///
|
||||
/// let numbers: [Double?] = [1.0, 2.0, nil, 3.0]
|
||||
/// numbers.publisher
|
||||
/// .replaceNil(with: 0.0)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "Optional(1.0) Optional(2.0) Optional(0.0) Optional(3.0)"
|
||||
///
|
||||
/// - Parameter output: The element to use when replacing `nil`.
|
||||
/// - Returns: A publisher that replaces `nil` elements from the upstream publisher
|
||||
/// with the provided element.
|
||||
public func replaceNil<ElementOfResult>(
|
||||
with output: ElementOfResult
|
||||
) -> Publishers.Map<Self, ElementOfResult>
|
||||
where Output == ElementOfResult?
|
||||
{
|
||||
return Publishers.Map(upstream: self) { $0 ?? output }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
@@ -218,13 +299,7 @@ extension Publishers.TryMap {
|
||||
return try downstream.receive(map(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
let subscription: Subscription?
|
||||
switch status {
|
||||
case let .subscribed(upstreamSubscription):
|
||||
subscription = upstreamSubscription
|
||||
case .awaitingSubscription, .terminal:
|
||||
subscription = nil
|
||||
}
|
||||
let subscription = status.subscription
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
|
||||
@@ -45,13 +45,44 @@ extension Publisher {
|
||||
|
||||
/// Converts any failure from the upstream publisher into a new error.
|
||||
///
|
||||
/// Until the upstream publisher finishes normally or fails with an error,
|
||||
/// the returned publisher republishes all the elements it receives.
|
||||
/// Use the `mapError(_:)` operator when you need to replace one error type with
|
||||
/// another, or where a downstream operator needs the error types of its inputs to
|
||||
/// match.
|
||||
///
|
||||
/// - Parameter transform: A closure that takes the upstream failure as a
|
||||
/// parameter and returns a new error for the publisher to terminate with.
|
||||
/// - Returns: A publisher that replaces any upstream failure with a
|
||||
/// new error produced by the `transform` closure.
|
||||
/// The following example uses a `tryMap(_:)` operator to divide `1` by each element
|
||||
/// produced by a sequence publisher. When the publisher produces a `0`,
|
||||
/// the `tryMap(_:)` fails with a `DivisionByZeroError`. The `mapError(_:)` operator
|
||||
/// converts this into a `MyGenericError`.
|
||||
///
|
||||
/// struct DivisionByZeroError: Error {}
|
||||
/// struct MyGenericError: Error { var wrappedError: Error }
|
||||
///
|
||||
/// func myDivide(_ dividend: Double, _ divisor: Double) throws -> Double {
|
||||
/// guard divisor != 0 else { throw DivisionByZeroError() }
|
||||
/// return dividend / divisor
|
||||
/// }
|
||||
///
|
||||
/// let divisors: [Double] = [5, 4, 3, 2, 1, 0]
|
||||
/// divisors.publisher
|
||||
/// .tryMap { try myDivide(1, $0) }
|
||||
/// .mapError { MyGenericError(wrappedError: $0) }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") ,
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints:
|
||||
/// // value: 0.2
|
||||
/// // value: 0.25
|
||||
/// // value: 0.3333333333333333
|
||||
/// // value: 0.5
|
||||
/// // value: 1.0
|
||||
/// // completion: failure(MyGenericError(wrappedError: DivisionByZeroError()))"
|
||||
///
|
||||
/// - Parameter transform: A closure that takes the upstream failure as a parameter
|
||||
/// and returns a new error for the publisher to terminate with.
|
||||
/// - Returns: A publisher that replaces any upstream failure with a new error
|
||||
/// produced by the `transform` closure.
|
||||
public func mapError<NewFailure: Error>(
|
||||
_ transform: @escaping (Failure) -> NewFailure
|
||||
) -> Publishers.MapError<Self, NewFailure>
|
||||
|
||||
@@ -12,6 +12,10 @@ from gyb_opencombine_support import (
|
||||
list_with_suffix_variadic
|
||||
)
|
||||
|
||||
import random
|
||||
|
||||
RNG = random.Random(0)
|
||||
|
||||
instantiations = [(1, '', ''),
|
||||
(2, 'two', 'second '),
|
||||
(3, 'three', 'third ')]
|
||||
@@ -41,18 +45,66 @@ extension Publisher {
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% doc_cardinal = 'a keyt path' if arity == 1 else cardinal + ' key paths'
|
||||
|
||||
/// Returns a publisher that publishes the values of ${doc_cardinal} as a tuple.
|
||||
% doc_cardinal = 'a key path' if arity == 1 else cardinal + ' key paths'
|
||||
%
|
||||
% doc_comment_suffix = 'value of the key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
%
|
||||
% doc_output_values = 'the value of this `Int`' \
|
||||
% if arity == 1 else 'these {} values (as an `({})` tuple)' \
|
||||
% .format(cardinal, ', '.join(['Int'] * arity))
|
||||
%
|
||||
% example_dice_roll_properties = ['die'] \
|
||||
% if arity == 1 else ['die{}'.format(i + 1) for i in range(arity)]
|
||||
% enumerated_example_dice_roll_properties = '`die` member' \
|
||||
% if arity == 1 else ('`die1` and `die2`' if arity == 2 else \
|
||||
% ''.join(['`{}`, '.format(s) for s in example_dice_roll_properties[:-1]]) + \
|
||||
% 'and `{}`'.format(example_dice_roll_properties[-1])) + ' members'
|
||||
%
|
||||
% dice_roll_init = ',\n /// ' \
|
||||
% .join([s + ': Int.random(in: 1...6)' for s in example_dice_roll_properties])
|
||||
/// Publishes the ${doc_comment_suffix}.
|
||||
///
|
||||
/// In the following example, the `map(${'_:' * arity})` operator uses the Swift
|
||||
/// key path syntax to access the ${enumerated_example_dice_roll_properties}
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// ${doc_output_values},
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
% for prop in example_dice_roll_properties:
|
||||
/// let ${prop}: Int
|
||||
% end
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(${dice_roll_init}))
|
||||
/// .map(${', '.join(['\.' + s for s in example_dice_roll_properties])})
|
||||
% if arity == 1:
|
||||
/// .sink {
|
||||
/// print ("Rolled: \($0)")
|
||||
/// }
|
||||
% else:
|
||||
% closure_args = ['values.{}'.format(i) for i in range(arity)]
|
||||
/// .sink { values in
|
||||
/// print("""
|
||||
/// Rolled: ${', '.join(['\\({})'.format(s) for s in closure_args])} \
|
||||
/// (total \(${' + '.join(closure_args)}))
|
||||
/// """)
|
||||
/// }
|
||||
% end
|
||||
% random_numbers = [RNG.randint(1, 6) for _ in range(arity)]
|
||||
% random_numbers_printed = ', '.join([str(i) for i in random_numbers]) + \
|
||||
% (' (or some other random value)' if arity == 1 \
|
||||
% else ' (total: {})" (or other random values)'.format(sum(random_numbers)))
|
||||
/// // Prints "Rolled: ${random_numbers_printed}.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity):
|
||||
% ordinal = 'another ' if i == 1 else 'a ' + instantiations[i][2]
|
||||
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`
|
||||
% ordinal = 'another ' if i == 1 and arity < 3 else 'a ' + instantiations[i][2]
|
||||
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`.
|
||||
% end
|
||||
%
|
||||
% doc_comment_suffix = 'value of the key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
/// - Returns: A publisher that publishes the ${doc_comment_suffix}.
|
||||
public func map<${cs_result_types}>(
|
||||
${method_args_joined}
|
||||
|
||||
@@ -10,11 +10,30 @@ extension Publisher {
|
||||
/// Measures and emits the time interval between events received from an upstream
|
||||
/// publisher.
|
||||
///
|
||||
/// The output type of the returned scheduler is the time interval of the provided
|
||||
/// Use `measureInterval(using:options:)` to measure the time between events delivered
|
||||
/// from an upstream publisher.
|
||||
///
|
||||
/// In the example below, a 1-second `Timer` is used as the data source for an event
|
||||
/// publisher; the `measureInterval(using:options:)` operator reports the elapsed time
|
||||
/// between the reception of events on the main run loop:
|
||||
///
|
||||
/// cancellable = Timer.publish(every: 1, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .measureInterval(using: RunLoop.main)
|
||||
/// .sink { print("\($0)", terminator: "\n") }
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Stride(magnitude: 1.0013610124588013)
|
||||
/// // Stride(magnitude: 0.9992760419845581)
|
||||
///
|
||||
/// The output type of the returned publisher is the time interval of the provided
|
||||
/// scheduler.
|
||||
///
|
||||
/// This operator uses the provided scheduler’s `now` property to measure intervals
|
||||
/// between events.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scheduler: The scheduler on which to deliver elements.
|
||||
/// - scheduler: A scheduler to use for tracking the timing of events.
|
||||
/// - options: Options that customize the delivery of elements.
|
||||
/// - Returns: A publisher that emits elements representing the time interval between
|
||||
/// the elements it receives.
|
||||
@@ -39,9 +58,15 @@ extension Publishers {
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The scheduler on which to deliver elements.
|
||||
/// The scheduler used for tracking the timing of events.
|
||||
public let scheduler: Context
|
||||
|
||||
/// Creates a publisher that measures and emits the time interval between events
|
||||
/// received from an upstream publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher from which this publisher receives elements.
|
||||
/// - scheduler: A scheduler to use for tracking the timing of events.
|
||||
public init(upstream: Upstream, scheduler: Context) {
|
||||
self.upstream = upstream
|
||||
self.scheduler = scheduler
|
||||
@@ -51,7 +76,7 @@ extension Publishers {
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Downstream.Input == Context.SchedulerTimeType.Stride
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
upstream.subscribe(Inner(scheduler: scheduler, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,27 +91,22 @@ extension Publishers.MeasureInterval {
|
||||
where Downstream.Input == Context.SchedulerTimeType.Stride,
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
typealias MeasureInterval = Publishers.MeasureInterval<Upstream, Context>
|
||||
|
||||
private enum State {
|
||||
case ready(MeasureInterval, Downstream)
|
||||
case subscribed(MeasureInterval, Downstream, Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state: State
|
||||
private let downstream: Downstream
|
||||
|
||||
private let scheduler: Context
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var last: Context.SchedulerTimeType?
|
||||
|
||||
init(_ measureInterval: MeasureInterval, downstream: Downstream) {
|
||||
state = .ready(measureInterval, downstream)
|
||||
init(scheduler: Context, downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
self.scheduler = scheduler
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -95,25 +115,26 @@ extension Publishers.MeasureInterval {
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(measureInterval, downstream) = state else {
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(measureInterval, downstream, subscription)
|
||||
last = measureInterval.scheduler.now
|
||||
state = .subscribed(subscription)
|
||||
last = scheduler.now
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(measureInterval, downstream, subscription) = state,
|
||||
let previousTime = last else {
|
||||
guard case let .subscribed(subscription) = state,
|
||||
let previousTime = last else
|
||||
{
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let now = measureInterval.scheduler.now
|
||||
let now = scheduler.now
|
||||
last = now
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(previousTime.distance(to: now))
|
||||
@@ -125,7 +146,7 @@ extension Publishers.MeasureInterval {
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -137,7 +158,7 @@ extension Publishers.MeasureInterval {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -147,7 +168,7 @@ extension Publishers.MeasureInterval {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,10 +11,45 @@ extension Publisher {
|
||||
///
|
||||
/// Use a multicast publisher when you have multiple downstream subscribers, but you
|
||||
/// want upstream publishers to only process one `receive(_:)` call per event.
|
||||
/// In contrast with `multicast(subject:)`, this method produces a publisher that
|
||||
/// creates a separate Subject for each subscriber.
|
||||
/// This is useful when upstream publishers are doing expensive work you don’t want
|
||||
/// to duplicate, like performing network requests.
|
||||
///
|
||||
/// - Parameter createSubject: A closure to create a new Subject each time
|
||||
/// In contrast with `multicast(subject:)`, this method produces a publisher that
|
||||
/// creates a separate `Subject` for each subscriber.
|
||||
///
|
||||
/// The following example uses a sequence publisher as a counter to publish three
|
||||
/// random numbers, generated by a `map(_:)` operator.
|
||||
/// It uses a `multicast(_:)` operator whose closure creates a `PassthroughSubject`
|
||||
/// to share the same random number to each of two subscribers. Because the multicast
|
||||
/// publisher is a `ConnectablePublisher`, publishing only begins after a call to
|
||||
/// `connect()`.
|
||||
///
|
||||
/// let pub = ["First", "Second", "Third"].publisher
|
||||
/// .map( { return ($0, Int.random(in: 0...100)) } )
|
||||
/// .print("Random")
|
||||
/// .multicast { PassthroughSubject<(String, Int), Never>() }
|
||||
///
|
||||
/// cancellable1 = pub
|
||||
/// .sink { print ("Stream 1 received: \($0)")}
|
||||
/// cancellable2 = pub
|
||||
/// .sink { print ("Stream 2 received: \($0)")}
|
||||
/// pub.connect()
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Random: receive value: (("First", 9))
|
||||
/// // Stream 2 received: ("First", 9)
|
||||
/// // Stream 1 received: ("First", 9)
|
||||
/// // Random: receive value: (("Second", 46))
|
||||
/// // Stream 2 received: ("Second", 46)
|
||||
/// // Stream 1 received: ("Second", 46)
|
||||
/// // Random: receive value: (("Third", 26))
|
||||
/// // Stream 2 received: ("Third", 26)
|
||||
/// // Stream 1 received: ("Third", 26)
|
||||
///
|
||||
/// In this example, the output shows that the `print(_:to:)` operator receives each
|
||||
/// random value only one time, and then sends the value to both subscribers.
|
||||
///
|
||||
/// - Parameter createSubject: A closure to create a new `Subject` each time
|
||||
/// a subscriber attaches to the multicast publisher.
|
||||
public func multicast<SubjectType: Subject>(
|
||||
_ createSubject: @escaping () -> SubjectType
|
||||
@@ -28,8 +63,42 @@ extension Publisher {
|
||||
///
|
||||
/// Use a multicast publisher when you have multiple downstream subscribers, but you
|
||||
/// want upstream publishers to only process one `receive(_:)` call per event.
|
||||
/// In contrast with `multicast(_:)`, this method produces a publisher shares
|
||||
/// the provided Subject among all the downstream subscribers.
|
||||
/// This is useful when upstream publishers are doing expensive work you don’t want
|
||||
/// to duplicate, like performing network requests.
|
||||
///
|
||||
/// In contrast with `multicast(_:)`, this method produces a publisher that shares
|
||||
/// the provided `Subject` among all the downstream subscribers.
|
||||
///
|
||||
/// The following example uses a sequence publisher as a counter to publish three
|
||||
/// random numbers, generated by a `map(_:)` operator.
|
||||
/// It uses a `multicast(subject:)` operator with a `PassthroughSubject` to share
|
||||
/// the same random number to each of two subscribers. Because the multicast publisher
|
||||
/// is a `ConnectablePublisher`, publishing only begins after a call to `connect()`.
|
||||
///
|
||||
/// let pub = ["First", "Second", "Third"].publisher
|
||||
/// .map( { return ($0, Int.random(in: 0...100)) } )
|
||||
/// .print("Random")
|
||||
/// .multicast(subject: PassthroughSubject<(String, Int), Never>())
|
||||
///
|
||||
/// cancellable1 = pub
|
||||
/// .sink { print ("Stream 1 received: \($0)")}
|
||||
/// cancellable2 = pub
|
||||
/// .sink { print ("Stream 2 received: \($0)")}
|
||||
/// pub.connect()
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Random: receive value: (("First", 78))
|
||||
/// // Stream 2 received: ("First", 78)
|
||||
/// // Stream 1 received: ("First", 78)
|
||||
/// // Random: receive value: (("Second", 98))
|
||||
/// // Stream 2 received: ("Second", 98)
|
||||
/// // Stream 1 received: ("Second", 98)
|
||||
/// // Random: receive value: (("Third", 61))
|
||||
/// // Stream 2 received: ("Third", 61)
|
||||
/// // Stream 1 received: ("Third", 61)
|
||||
///
|
||||
/// In this example, the output shows that the `print(_:to:)` operator receives each
|
||||
/// random value only one time, and then sends the value to both subscribers.
|
||||
///
|
||||
/// - Parameter subject: A subject to deliver elements to downstream subscribers.
|
||||
public func multicast<SubjectType: Subject>(
|
||||
@@ -44,18 +113,19 @@ extension Publisher {
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that uses a subject to deliver elements to multiple subscribers.
|
||||
///
|
||||
/// Use a multicast publisher when you have multiple downstream subscribers, but you
|
||||
/// want upstream publishers to only process one `receive(_:)` call per event.
|
||||
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
|
||||
: ConnectablePublisher
|
||||
where Upstream.Failure == SubjectType.Failure,
|
||||
Upstream.Output == SubjectType.Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure to create a new Subject each time a subscriber attaches
|
||||
@@ -79,12 +149,10 @@ extension Publishers {
|
||||
return subject
|
||||
}
|
||||
|
||||
/// Creates a multicast publisher that applies a closure to create a subject
|
||||
/// that delivers elements to subscribers.
|
||||
/// Creates a multicast publisher that applies a closure to create a subject that
|
||||
/// delivers elements to subscribers.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
/// - Parameter createSubject: A closure to create a new Subject each time
|
||||
/// - Parameter createSubject: A closure that returns a `Subject` each time
|
||||
/// a subscriber attaches to the multicast publisher.
|
||||
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
|
||||
self.upstream = upstream
|
||||
|
||||
@@ -9,6 +9,19 @@ extension Publisher {
|
||||
|
||||
/// Republishes elements up to the specified maximum count.
|
||||
///
|
||||
/// Use `prefix(_:)` to limit the number of elements republished to the downstream
|
||||
/// subscriber.
|
||||
///
|
||||
/// In the example below, the `prefix(_:)` operator limits its output to the first
|
||||
/// two elements before finishing normally:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .prefix(2)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1"
|
||||
///
|
||||
/// - Parameter maxLength: The maximum number of elements to republish.
|
||||
/// - Returns: A publisher that publishes up to the specified number of elements
|
||||
/// before completing.
|
||||
@@ -22,8 +35,20 @@ extension Publisher {
|
||||
/// Publishes a specific element, indicated by its index in the sequence of published
|
||||
/// elements.
|
||||
///
|
||||
/// If the publisher completes normally or with an error before publishing
|
||||
/// the specified element, then the publisher doesn’t produce any elements.
|
||||
/// Use `output(at:)` when you need to republish a specific element specified by
|
||||
/// its position in the stream. If the publisher completes normally or with an error
|
||||
/// before publishing the specified element, then the publisher doesn’t produce any
|
||||
/// elements.
|
||||
///
|
||||
/// In the example below, the array publisher emits the fifth element in the sequence
|
||||
/// of published elements:
|
||||
///
|
||||
/// let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
/// numbers.publisher
|
||||
/// .output(at: 5)
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "6"
|
||||
///
|
||||
/// - Parameter index: The index that indicates the element to publish.
|
||||
/// - Returns: A publisher that publishes a specific indexed element.
|
||||
@@ -33,10 +58,21 @@ extension Publisher {
|
||||
|
||||
/// Publishes elements specified by their range in the sequence of published elements.
|
||||
///
|
||||
/// After all elements are published, the publisher finishes normally.
|
||||
/// Use `output(in:)` to republish a range indices you specify in the published
|
||||
/// stream. After publishing all elements, the publisher finishes normally.
|
||||
/// If the publisher completes normally or with an error before producing all
|
||||
/// the elements in the range, it doesn’t publish the remaining elements.
|
||||
///
|
||||
/// In the example below, an array publisher emits the subset of elements at
|
||||
/// the indices in the specified range:
|
||||
///
|
||||
/// let numbers = [1, 1, 2, 2, 2, 3, 4, 5, 6]
|
||||
/// numbers.publisher
|
||||
/// .output(in: (3...5))
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "2 2 3"
|
||||
///
|
||||
/// - Parameter range: A range that indicates which elements to publish.
|
||||
/// - Returns: A publisher that publishes elements specified by a range.
|
||||
public func output<Range: RangeExpression>(in range: Range) -> Publishers.Output<Self>
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// Publishers.PrefixUntilOutput.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.11.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements until another publisher emits an element.
|
||||
///
|
||||
/// After the second publisher publishes an element, the publisher returned by this
|
||||
/// method finishes.
|
||||
///
|
||||
/// - Parameter publisher: A second publisher.
|
||||
/// - Returns: A publisher that republishes elements until the second publisher
|
||||
/// publishes an element.
|
||||
public func prefix<Other: Publisher>(
|
||||
untilOutputFrom publisher: Other
|
||||
) -> Publishers.PrefixUntilOutput<Self, Other> {
|
||||
return .init(upstream: self, other: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct PrefixUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Another publisher, whose first output causes this publisher to finish.
|
||||
public let other: Other
|
||||
|
||||
public init(upstream: Upstream, other: Other) {
|
||||
self.upstream = upstream
|
||||
self.other = other
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, trigger: other))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.PrefixUntilOutput {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private struct Termination: Subscriber {
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.terminationReceive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Other.Output) -> Subscribers.Demand {
|
||||
return inner.terminationReceive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Other.Failure>) {
|
||||
inner.terminationReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private var termination: Termination?
|
||||
private var prefixState = SubscriptionStatus.awaitingSubscription
|
||||
private var terminationState = SubscriptionStatus.awaitingSubscription
|
||||
private var triggered = false
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let downstream: Downstream
|
||||
|
||||
init(downstream: Downstream, trigger: Other) {
|
||||
self.downstream = downstream
|
||||
let termination = Termination(inner: self)
|
||||
self.termination = termination
|
||||
trigger.subscribe(termination)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = prefixState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
prefixState = triggered ? .terminal : .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = prefixState else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
let prefixState = self.prefixState
|
||||
let terminationSubscription = terminationState.subscription
|
||||
self.prefixState = .terminal
|
||||
terminationState = .terminal
|
||||
termination = nil
|
||||
lock.unlock()
|
||||
terminationSubscription?.cancel()
|
||||
if case .subscribed = prefixState {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = prefixState else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let prefixSubscription = prefixState.subscription
|
||||
let terminationSubscription = terminationState.subscription
|
||||
prefixState = .terminal
|
||||
terminationState = .terminal
|
||||
lock.unlock()
|
||||
prefixSubscription?.cancel()
|
||||
terminationSubscription?.cancel()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func terminationReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = terminationState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
terminationState = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
subscription.request(.max(1))
|
||||
}
|
||||
|
||||
private func terminationReceive(_ input: Other.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = terminationState else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let prefixSubscription = prefixState.subscription
|
||||
prefixState = .terminal
|
||||
terminationState = .terminal
|
||||
termination = nil
|
||||
triggered = true
|
||||
lock.unlock()
|
||||
prefixSubscription?.cancel()
|
||||
downstream.receive(completion: .finished)
|
||||
return .none
|
||||
}
|
||||
|
||||
private func terminationReceive(
|
||||
completion: Subscribers.Completion<Other.Failure>
|
||||
) {
|
||||
lock.lock()
|
||||
terminationState = .terminal
|
||||
termination = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,22 @@ extension Publisher {
|
||||
/// Republishes elements while a predicate closure indicates publishing should
|
||||
/// continue.
|
||||
///
|
||||
/// The publisher finishes when the closure returns `false`.
|
||||
/// Use `prefix(while:)` to emit values while elements from the upstream publishe
|
||||
/// meet a condition you specify. The publisher finishes when the closure returns
|
||||
/// `false`.
|
||||
///
|
||||
/// In the example below, the `prefix(while:)` operator emits values while the element
|
||||
/// it receives is less than five:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// numbers.publisher
|
||||
/// .prefix { $0 < 5 }
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4"
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether publishing should continue.
|
||||
/// returns a Boolean value that indicates whether publishing should continue.
|
||||
/// - Returns: A publisher that passes through elements until the predicate indicates
|
||||
/// publishing should finish.
|
||||
public func prefix(
|
||||
@@ -22,11 +34,28 @@ extension Publisher {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Republishes elements while a error-throwing predicate closure indicates publishing
|
||||
/// should continue.
|
||||
/// Republishes elements while an error-throwing predicate closure indicates
|
||||
/// publishing should continue.
|
||||
///
|
||||
/// The publisher finishes when the closure returns `false`. If the closure throws,
|
||||
/// the publisher fails with the thrown error.
|
||||
/// Use `tryPrefix(while:)` to emit values from the upstream publisher that meet
|
||||
/// a condition you specify in an error-throwing closure.
|
||||
/// The publisher finishes when the closure returns `false`. If the closure throws
|
||||
/// an error, the publisher fails with that error.
|
||||
///
|
||||
/// struct OutOfRangeError: Error {}
|
||||
///
|
||||
/// let numbers = (0...10).reversed()
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryPrefix {
|
||||
/// guard $0 != 0 else { throw OutOfRangeError() }
|
||||
/// return $0 <= numbers.max()!
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)", terminator: " ") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "10 9 8 7 6 5 4 3 2 1 completion: failure(OutOfRangeError()) "
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether publishing should continue.
|
||||
|
||||
@@ -9,8 +9,27 @@ extension Publisher {
|
||||
|
||||
/// Prints log messages for all publishing events.
|
||||
///
|
||||
/// - Parameter prefix: A string with which to prefix all log messages. Defaults to
|
||||
/// an empty string.
|
||||
/// Use `print(_:to:)` to log messages the console.
|
||||
///
|
||||
/// In the example below, log messages are printed on the console:
|
||||
///
|
||||
/// let integers = (1...2)
|
||||
/// cancellable = integers.publisher
|
||||
/// .print("Logged a message", to: nil)
|
||||
/// .sink { _ in }
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Logged a message: receive subscription: (1..<2)
|
||||
/// // Logged a message: request unlimited
|
||||
/// // Logged a message: receive value: (1)
|
||||
/// // Logged a message: receive finished
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - prefix: A string — which defaults to empty — with which to prefix all log
|
||||
/// messages.
|
||||
/// - stream: A stream for text output that receives messages, and which directs
|
||||
/// output to the console by default. A custom stream can be used to log messages
|
||||
/// to other destinations.
|
||||
/// - Returns: A publisher that prints log messages for all publishing events.
|
||||
public func print(_ prefix: String = "",
|
||||
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
|
||||
@@ -24,11 +43,12 @@ extension Publishers {
|
||||
/// prefixed with a given string.
|
||||
///
|
||||
/// This publisher prints log messages when receiving the following events:
|
||||
/// * subscription
|
||||
/// * value
|
||||
/// * normal completion
|
||||
/// * failure
|
||||
/// * cancellation
|
||||
///
|
||||
/// - subscription
|
||||
/// - value
|
||||
/// - normal completion
|
||||
/// - failure
|
||||
/// - cancellation
|
||||
public struct Print<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
extension Publisher {
|
||||
/// Specifies the scheduler on which to receive elements from the publisher.
|
||||
///
|
||||
/// You use the `receive(on:options:)` operator to receive results on a specific
|
||||
/// scheduler, such as performing UI work on the main run loop.
|
||||
/// In contrast with `subscribe(on:options:)`, which affects upstream messages,
|
||||
/// You use the `receive(on:options:)` operator to receive results and completion on
|
||||
/// a specific scheduler, such as performing UI work on the main run loop. In contrast
|
||||
/// with `subscribe(on:options:)`, which affects upstream messages,
|
||||
/// `receive(on:options:)` changes the execution context of downstream messages.
|
||||
/// In the following example, requests to `jsonPublisher` are performed on
|
||||
/// `backgroundQueue`, but elements received from it are performed on `RunLoop.main`.
|
||||
///
|
||||
/// In the following example, the `subscribe(on:options:)` operator causes
|
||||
/// `jsonPublisher` to receive requests on `backgroundQueue`, while
|
||||
/// the `receive(on:options:)` causes `labelUpdater` to receive elements and
|
||||
/// completion on `RunLoop.main`.
|
||||
///
|
||||
/// // Some publisher.
|
||||
/// let jsonPublisher = MyJSONLoaderPublisher()
|
||||
@@ -23,12 +26,31 @@ extension Publisher {
|
||||
///
|
||||
/// jsonPublisher
|
||||
/// .subscribe(on: backgroundQueue)
|
||||
/// .receiveOn(on: RunLoop.main)
|
||||
/// .receive(on: RunLoop.main)
|
||||
/// .subscribe(labelUpdater)
|
||||
///
|
||||
///
|
||||
/// Prefer `receive(on:options:)` over explicit use of dispatch queues when performing
|
||||
/// work in subscribers. For example, instead of the following pattern:
|
||||
///
|
||||
/// pub.sink {
|
||||
/// DispatchQueue.main.async {
|
||||
/// // Do something.
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Use this pattern instead:
|
||||
///
|
||||
/// pub.receive(on: DispatchQueue.main).sink {
|
||||
/// // Do something.
|
||||
/// }
|
||||
///
|
||||
/// > Note: `receive(on:options:)` doesn’t affect the scheduler used to cal
|
||||
/// the subscriber’s `receive(subscription:)` method.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scheduler: The scheduler the publisher is to use for element delivery.
|
||||
/// - options: Scheduler options that customize the element delivery.
|
||||
/// - scheduler: The scheduler the publisher uses for element delivery.
|
||||
/// - options: Scheduler options used to customize element delivery.
|
||||
/// - Returns: A publisher that delivers elements using the specified scheduler.
|
||||
public func receive<Context: Scheduler>(
|
||||
on scheduler: Context,
|
||||
@@ -69,7 +91,10 @@ extension Publishers {
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
let inner = Inner(scheduler: scheduler,
|
||||
options: options,
|
||||
downstream: subscriber)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,23 +109,21 @@ extension Publishers.ReceiveOn {
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
typealias ReceiveOn = Publishers.ReceiveOn<Upstream, Context>
|
||||
|
||||
private enum State {
|
||||
case ready(ReceiveOn, Downstream)
|
||||
case subscribed(ReceiveOn, Downstream, Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state: State
|
||||
private let downstream: Downstream
|
||||
private let scheduler: Context
|
||||
private let options: Context.SchedulerOptions?
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
init(_ receiveOn: ReceiveOn, downstream: Downstream) {
|
||||
state = .ready(receiveOn, downstream)
|
||||
init(scheduler: Context,
|
||||
options: Context.SchedulerOptions?,
|
||||
downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -110,62 +133,65 @@ extension Publishers.ReceiveOn {
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(receiveOn, downstream) = state else {
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(receiveOn, downstream, subscription)
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(receiveOn, downstream, _) = state else {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) {
|
||||
self.scheduledReceive(input, downstream: downstream)
|
||||
scheduler.schedule(options: options) {
|
||||
self.scheduledReceive(input)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
private func scheduledReceive(_ input: Upstream.Output, downstream: Downstream) {
|
||||
private func scheduledReceive(_ input: Input) {
|
||||
lock.lock()
|
||||
guard state.subscription != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else {
|
||||
return
|
||||
}
|
||||
if newDemand == .none { return }
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let subscription = state.subscription
|
||||
lock.unlock()
|
||||
subscription.request(newDemand)
|
||||
subscription?.request(newDemand)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(receiveOn, downstream, _) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .pendingTerminal(subscription)
|
||||
lock.unlock()
|
||||
scheduler.schedule(options: options) {
|
||||
self.scheduledReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) {
|
||||
self.scheduledReceive(completion: completion, downstream: downstream)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>,
|
||||
downstream: Downstream) {
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
@@ -173,7 +199,7 @@ extension Publishers.ReceiveOn {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -183,7 +209,7 @@ extension Publishers.ReceiveOn {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,15 +7,32 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Applies a closure that accumulates each element of a stream and publishes
|
||||
/// a final result upon completion.
|
||||
/// Applies a closure that collects each element of a stream and publishes a final
|
||||
/// result upon completion.
|
||||
///
|
||||
/// Use `reduce(_:_:)` to collect a stream of elements and produce an accumulated
|
||||
/// value based on a closure you provide.
|
||||
///
|
||||
/// In the following example, the `reduce(_:_:)` operator collects all the integer
|
||||
/// values it receives from its upstream publisher:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .reduce(0, { accum, next in accum + next })
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "55"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The value the closure receives the first time it is called.
|
||||
/// - nextPartialResult: A closure that takes the previously-accumulated value and
|
||||
/// the next element from the upstream publisher to produce a new value.
|
||||
/// - initialResult: The value that the closure receives the first time it’s called.
|
||||
/// - nextPartialResult: A closure that produces a new value by taking
|
||||
/// the previously-accumulated value and the next element it receives from
|
||||
/// the upstream publisher.
|
||||
/// - Returns: A publisher that applies the closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
/// If `reduce(_:_:)` receives an error from the upstream publisher, the operator
|
||||
/// delivers it to the downstream subscriber, the publisher terminates and publishes
|
||||
/// no value.
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Output) -> Accumulator
|
||||
@@ -25,17 +42,36 @@ extension Publisher {
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
|
||||
/// Applies an error-throwing closure that accumulates each element of a stream and
|
||||
/// Applies an error-throwing closure that collects each element of a stream and
|
||||
/// publishes a final result upon completion.
|
||||
///
|
||||
/// If the closure throws an error, the publisher fails, passing the error
|
||||
/// to its subscriber.
|
||||
/// Use `tryReduce(_:_:)` to collect a stream of elements and produce an accumulated
|
||||
/// value based on an error-throwing closure you provide.
|
||||
/// If the closure throws an error, the publisher fails and passes the error to its
|
||||
/// subscriber.
|
||||
///
|
||||
/// In the example below, the publisher’s `0` element causes the `myDivide(_:_:)`
|
||||
/// function to throw an error and publish the `Double.nan` result:
|
||||
///
|
||||
/// struct DivisionByZeroError: Error {}
|
||||
/// func myDivide(_ dividend: Double, _ divisor: Double) throws -> Double {
|
||||
/// guard divisor != 0 else { throw DivisionByZeroError() }
|
||||
/// return dividend / divisor
|
||||
/// }
|
||||
///
|
||||
/// var numbers: [Double] = [5, 4, 3, 2, 1, 0]
|
||||
/// numbers.publisher
|
||||
/// .tryReduce(numbers.first!, { accum, next in try myDivide(accum, next) })
|
||||
/// .catch({ _ in Just(Double.nan) })
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
|
||||
/// - Parameters:
|
||||
/// - initialResult: The value the closure receives the first time it is called.
|
||||
/// - initialResult: The value that the closure receives the first time it’s called.
|
||||
/// - nextPartialResult: An error-throwing closure that takes
|
||||
/// the previously-accumulated value and the next element from the upstream
|
||||
/// publisher to produce a new value.
|
||||
///
|
||||
/// - Returns: A publisher that applies the closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
public func tryReduce<Accumulator>(
|
||||
|
||||
@@ -9,7 +9,23 @@ extension Publisher where Output: Equatable {
|
||||
|
||||
/// Publishes only elements that don’t match the previous element.
|
||||
///
|
||||
/// - Returns: A publisher that consumes — rather than publishes — duplicate elements.
|
||||
/// Use `removeDuplicates()` to remove repeating elements from an upstream publisher.
|
||||
/// This operator has a two-element memory: the operator uses the current and
|
||||
/// previously published elements as the basis for its comparison.
|
||||
///
|
||||
/// In the example below, `removeDuplicates()` triggers on the doubled, tripled, and
|
||||
/// quadrupled occurrences of `1`, `3`, and `4` respectively. Because the two-element
|
||||
/// memory considers only the current element and the previous element, the operator
|
||||
/// prints the final `0` in the example data since its immediate predecessor is `4`.
|
||||
///
|
||||
/// let numbers = [0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 0]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .removeDuplicates()
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4 0"
|
||||
///
|
||||
/// - Returns: A publisher that consumes — rather than publishes — duplicate elements.
|
||||
public func removeDuplicates() -> Publishers.RemoveDuplicates<Self> {
|
||||
return removeDuplicates(by: ==)
|
||||
}
|
||||
@@ -20,9 +36,39 @@ extension Publisher {
|
||||
/// Publishes only elements that don’t match the previous element, as evaluated by
|
||||
/// a provided closure.
|
||||
///
|
||||
/// Use `removeDuplicates(by:)` to remove repeating elements from an upstream
|
||||
/// publisher based upon the evaluation of the current and previously published
|
||||
/// elements using a closure you provide.
|
||||
///
|
||||
/// Use the `removeDuplicates(by:)` operator when comparing types that don’t
|
||||
/// themselves implement `Equatable`, or if you need to compare values differently
|
||||
/// than the type’s `Equatable` implementation.
|
||||
///
|
||||
/// In the example below, the `removeDuplicates(by:)` functionality triggers when
|
||||
/// the `x` property of the current and previous elements are equal, otherwise
|
||||
/// the operator publishes the current `Point` to the downstream subscriber:
|
||||
///
|
||||
/// struct Point {
|
||||
/// let x: Int
|
||||
/// let y: Int
|
||||
/// }
|
||||
///
|
||||
/// let points = [Point(x: 0, y: 0), Point(x: 0, y: 1),
|
||||
/// Point(x: 1, y: 1), Point(x: 2, y: 1)]
|
||||
/// cancellable = points.publisher
|
||||
/// .removeDuplicates { prev, current in
|
||||
/// // Considers points to be duplicate if the x coordinate
|
||||
/// // is equal, and ignores the y coordinate
|
||||
/// prev.x == current.x
|
||||
/// }
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: Point(x: 0, y: 0) Point(x: 1, y: 1) Point(x: 2, y: 1)
|
||||
///
|
||||
/// - Parameter predicate: A closure to evaluate whether two elements are equivalent,
|
||||
/// for purposes of filtering. Return `true` from this closure to indicate that
|
||||
/// the second element is a duplicate of the first.
|
||||
/// - Returns: A publisher that consumes — rather than publishes — duplicate elements.
|
||||
public func removeDuplicates(
|
||||
by predicate: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.RemoveDuplicates<Self> {
|
||||
@@ -32,10 +78,37 @@ extension Publisher {
|
||||
/// Publishes only elements that don’t match the previous element, as evaluated by
|
||||
/// a provided error-throwing closure.
|
||||
///
|
||||
/// Use `tryRemoveDuplicates(by:)` to remove repeating elements from an upstream
|
||||
/// publisher based upon the evaluation of elements using an error-throwing closure
|
||||
/// you provide. If your closure throws an error, the publisher terminates with
|
||||
/// the error.
|
||||
///
|
||||
/// In the example below, the closure provided to `tryRemoveDuplicates(by:)` returns
|
||||
/// `true` when two consecutive elements are equal, thereby filtering out `0`,
|
||||
/// `1`, `2`, and `3`. However, the closure throws an error when it encounters `4`.
|
||||
/// The publisher then terminates with this error.
|
||||
///
|
||||
/// struct BadValuesError: Error {}
|
||||
/// let numbers = [0, 0, 0, 0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryRemoveDuplicates { first, second -> Bool in
|
||||
/// if (first == 4 && second == 4) {
|
||||
/// throw BadValuesError()
|
||||
/// }
|
||||
/// return first == second
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("\($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4 failure(BadValuesError()"
|
||||
///
|
||||
/// - Parameter predicate: A closure to evaluate whether two elements are equivalent,
|
||||
/// for purposes of filtering. Return `true` from this closure to indicate that
|
||||
/// the second element is a duplicate of the first. If this closure throws an error,
|
||||
/// the publisher terminates with the thrown error.
|
||||
/// - Returns: A publisher that consumes — rather than publishes — duplicate elements.
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryRemoveDuplicates<Self> {
|
||||
@@ -55,8 +128,7 @@ extension Publishers {
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure to evaluate whether two elements are equivalent,
|
||||
/// for purposes of filtering.
|
||||
/// The predicate closure used to evaluate whether two elements are duplicates.
|
||||
public let predicate: (Output, Output) -> Bool
|
||||
|
||||
/// Creates a publisher that publishes only elements that don’t match the previous
|
||||
@@ -82,7 +154,7 @@ extension Publishers {
|
||||
|
||||
/// A publisher that publishes only elements that don’t match the previous element,
|
||||
/// as evaluated by a provided error-throwing closure.
|
||||
public struct TryRemoveDuplicates<Upstream: Publisher>: Publisher{
|
||||
public struct TryRemoveDuplicates<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
@@ -128,8 +200,6 @@ extension Publishers.RemoveDuplicates {
|
||||
(Output, Output) -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
private var last: Upstream.Output?
|
||||
|
||||
override func receive(
|
||||
@@ -163,8 +233,6 @@ extension Publishers.TryRemoveDuplicates {
|
||||
(Output, Output) throws -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
private var last: Upstream.Output?
|
||||
|
||||
override func receive(
|
||||
|
||||
@@ -9,12 +9,33 @@ extension Publisher {
|
||||
|
||||
/// Replaces an empty stream with the provided element.
|
||||
///
|
||||
/// If the upstream publisher finishes without producing any elements,
|
||||
/// this publisher emits the provided element, then finishes normally.
|
||||
/// - Parameter output: An element to emit when the upstream publisher
|
||||
/// finishes without emitting any elements.
|
||||
/// - Returns: A publisher that replaces an empty stream with
|
||||
/// the provided output element.
|
||||
/// Use `replaceEmpty(with:)` to provide a replacement element if the upstream
|
||||
/// publisher finishes without producing any elements.
|
||||
///
|
||||
/// In the example below, the empty `Double` array publisher doesn’t produce any
|
||||
/// elements, so `replaceEmpty(with:)` publishes `Double.nan` and finishes normally.
|
||||
///
|
||||
/// let numbers: [Double] = []
|
||||
/// cancellable = numbers.publisher
|
||||
/// .replaceEmpty(with: Double.nan)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints "(nan)".
|
||||
///
|
||||
/// Conversely, providing a non-empty publisher publishes all elements and
|
||||
/// the publisher then terminates normally:
|
||||
///
|
||||
/// let otherNumbers: [Double] = [1.0, 2.0, 3.0]
|
||||
/// cancellable2 = otherNumbers.publisher
|
||||
/// .replaceEmpty(with: Double.nan)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: 1.0 2.0 3.0
|
||||
///
|
||||
/// - Parameter output: An element to emit when the upstream publisher finishes
|
||||
/// without emitting any elements.
|
||||
/// - Returns: A publisher that replaces an empty stream with the provided output
|
||||
/// element.
|
||||
public func replaceEmpty(with output: Output) -> Publishers.ReplaceEmpty<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,36 @@
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces any errors in the stream with the provided element.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher emits the provided
|
||||
/// element, then finishes normally.
|
||||
///
|
||||
/// In the example below, a publisher of strings fails with a `MyError` instance,
|
||||
/// which sends a failure completion downstream. The `replaceError(with:)` operator
|
||||
/// handles the failure by publishing the string `(replacement element)` and
|
||||
/// completing normally.
|
||||
///
|
||||
/// struct MyError: Error {}
|
||||
/// let fail = Fail<String, MyError>(error: MyError())
|
||||
/// cancellable = fail
|
||||
/// .replaceError(with: "(replacement element)")
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("\($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "(replacement element) finished".
|
||||
///
|
||||
/// This `replaceError(with:)` functionality is useful when you want to handle
|
||||
/// an error by sending a single replacement element and end the stream.
|
||||
/// Use `catch(_:)` to recover from an error and provide a replacement publisher
|
||||
/// to continue providing elements to the downstream subscriber.
|
||||
///
|
||||
/// - Parameter output: An element to emit when the upstream publisher fails.
|
||||
/// - Returns: A publisher that replaces an error from the upstream publisher with
|
||||
/// the provided output element.
|
||||
/// the provided output element.
|
||||
public func replaceError(with output: Output) -> Publishers.ReplaceError<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
@@ -73,8 +96,6 @@ extension Publishers.ReplaceError {
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
@@ -104,12 +125,12 @@ extension Publishers.ReplaceError {
|
||||
status = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
lock.unlock()
|
||||
if pendingDemand > 0 {
|
||||
if pendingDemand != .none {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
@@ -118,7 +139,7 @@ extension Publishers.ReplaceError {
|
||||
pendingDemand -= 1
|
||||
lock.unlock()
|
||||
let demand = downstream.receive(input)
|
||||
guard demand > 0 else {
|
||||
if demand == .none {
|
||||
return .none
|
||||
}
|
||||
lock.lock()
|
||||
@@ -127,20 +148,27 @@ extension Publishers.ReplaceError {
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
switch completion {
|
||||
case .finished:
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure:
|
||||
lock.lock()
|
||||
// If there was no demand from downstream,
|
||||
// ReplaceError does not forward the value that
|
||||
// replaces the error until it is requested.
|
||||
guard pendingDemand > 0 else {
|
||||
if pendingDemand == .none {
|
||||
terminated = true
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// Publishers.ReplaceNil.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces nil elements in the stream with the provided element.
|
||||
///
|
||||
/// - Parameter output: The element to use when replacing `nil`.
|
||||
/// - Returns: A publisher that replaces `nil` elements from
|
||||
/// the upstream publisher with the provided element.
|
||||
public func replaceNil<ElementOfResult>(
|
||||
with output: ElementOfResult
|
||||
) -> Publishers.Map<Self, ElementOfResult>
|
||||
where Output == ElementOfResult?
|
||||
{
|
||||
return Publishers.Map(upstream: self) { $0 ?? output }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
//
|
||||
// Publishers.Retry.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.06.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Attempts to recreate a failed subscription with the upstream publisher up to
|
||||
/// the number of times you specify.
|
||||
///
|
||||
/// Use `retry(_:)` to try connecting to an upstream publisher after a failed
|
||||
/// connection attempt.
|
||||
///
|
||||
/// In the example below, a `URLSession.DataTaskPublisher` attempts to connect to
|
||||
/// a remote URL. If the connection attempt succeeds, it publishes the remote
|
||||
/// service’s HTML to the downstream publisher and completes normally. Otherwise,
|
||||
/// the retry operator attempts to reestablish the connection. If after three attempts
|
||||
/// the publisher still can’t connect to the remote URL, the `catch(_:)` operator
|
||||
/// replaces the error with a new publisher that publishes a “connection timed out”
|
||||
/// HTML page. After the downstream subscriber receives the timed out message,
|
||||
/// the stream completes normally.
|
||||
///
|
||||
/// struct WebSiteData: Codable {
|
||||
/// var rawHTML: String
|
||||
/// }
|
||||
///
|
||||
/// let myURL = URL(string: "https://www.example.com")
|
||||
///
|
||||
/// cancellable = URLSession.shared.dataTaskPublisher(for: myURL!)
|
||||
/// .retry(3)
|
||||
/// .map { page -> WebSiteData in
|
||||
/// WebSiteData(rawHTML: String(decoding: page.data, as: UTF8.self))
|
||||
/// }
|
||||
/// .catch { error in
|
||||
/// Just(
|
||||
/// WebSiteData(
|
||||
/// rawHTML: "<HTML>Unable to load page - timed out.</HTML>"
|
||||
/// )
|
||||
/// )
|
||||
/// }
|
||||
/// .sink(receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") })
|
||||
///
|
||||
/// // Prints: The HTML content from the remote URL upon a successful connection,
|
||||
/// // or returns "<HTML>Unable to load page - timed out.</HTML>" if
|
||||
/// // the number of retries exceeds the specified value.
|
||||
///
|
||||
/// After exceeding the specified number of retries, the publisher passes the failure
|
||||
/// to the downstream receiver.
|
||||
/// - Parameter retries: The number of times to attempt to recreate the subscription.
|
||||
/// - Returns: A publisher that attempts to recreate its subscription to a failed
|
||||
/// upstream publisher.
|
||||
public func retry(_ retries: Int) -> Publishers.Retry<Self> {
|
||||
return .init(upstream: self, retries: retries)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that attempts to recreate its subscription to a failed upstream
|
||||
/// publisher.
|
||||
public struct Retry<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 retry attempts to perform.
|
||||
///
|
||||
/// If `nil`, this publisher attempts to reconnect with the upstream publisher
|
||||
/// an unlimited number of times.
|
||||
public let retries: Int?
|
||||
|
||||
/// Creates a publisher that attempts to recreate its subscription to a failed
|
||||
/// upstream publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher from which this publisher receives its elements.
|
||||
/// - retries: The maximum number of retry attempts to perform. If `nil`, this
|
||||
/// publisher attempts to reconnect with the upstream publisher an unlimited
|
||||
/// number of times.
|
||||
public init(upstream: Upstream, retries: Int?) {
|
||||
self.upstream = upstream
|
||||
self.retries = retries
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(parent: self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Retry: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Retry {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case ready(Publishers.Retry<Upstream>, Downstream)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private enum Chances {
|
||||
case finite(Int)
|
||||
case infinite
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state: State
|
||||
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
private var remaining: Chances
|
||||
|
||||
private var downstreamNeedsSubscription = true
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
private var completionRecursion = false
|
||||
|
||||
private var needsSubscribe = false
|
||||
|
||||
init(parent: Publishers.Retry<Upstream>, downstream: Downstream) {
|
||||
state = .ready(parent, downstream)
|
||||
remaining = parent.retries.map(Chances.finite) ?? .infinite
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(_, downstream) = state, upstreamSubscription == nil
|
||||
else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = subscription
|
||||
let downstreamDemand = self.downstreamDemand
|
||||
let downstreamNeedsSubscription = self.downstreamNeedsSubscription
|
||||
self.downstreamNeedsSubscription = false
|
||||
lock.unlock()
|
||||
if downstreamNeedsSubscription {
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
if downstreamDemand != .none {
|
||||
subscription.request(downstreamDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .ready(_, downstream) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
downstreamDemand -= 1
|
||||
lock.unlock()
|
||||
|
||||
let newDemand = downstream.receive(input)
|
||||
|
||||
if newDemand == .none { return .none }
|
||||
|
||||
lock.lock()
|
||||
downstreamDemand += newDemand
|
||||
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
lock.unlock()
|
||||
upstreamSubscription.request(newDemand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .ready(parent, downstream) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if case .failure = completion {
|
||||
upstreamSubscription = nil
|
||||
switch remaining {
|
||||
case .finite(0):
|
||||
break
|
||||
case .finite(let attempts):
|
||||
remaining = .finite(attempts - 1)
|
||||
fallthrough
|
||||
case .infinite:
|
||||
if completionRecursion {
|
||||
needsSubscribe = true
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
repeat {
|
||||
completionRecursion = true
|
||||
needsSubscribe = false
|
||||
lock.unlock()
|
||||
parent.upstream.subscribe(self)
|
||||
lock.lock()
|
||||
completionRecursion = false
|
||||
} while needsSubscribe
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case .ready = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamDemand += demand
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
lock.unlock()
|
||||
upstreamSubscription.request(demand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case .ready = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
lock.unlock()
|
||||
upstreamSubscription.cancel()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "Retry" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,20 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms elements from the upstream publisher by providing the current element
|
||||
/// to a closure along with the last value returned by the closure.
|
||||
/// Transforms elements from the upstream publisher by providing the current
|
||||
/// element to a closure along with the last value returned by the closure.
|
||||
///
|
||||
/// let pub = (0...5)
|
||||
/// .publisher
|
||||
/// .scan(0, { return $0 + $1 })
|
||||
/// .sink(receiveValue: { print ("\($0)", terminator: " ") })
|
||||
/// // Prints "0 1 3 6 10 15 ".
|
||||
/// Use `scan(_:_:)` to accumulate all previously-published values into a single
|
||||
/// value, which you then combine with each newly-published value.
|
||||
///
|
||||
/// The following example logs a running total of all values received
|
||||
/// from the sequence publisher.
|
||||
///
|
||||
/// let range = (0...5)
|
||||
/// cancellable = range.publisher
|
||||
/// .scan(0) { return $0 + $1 }
|
||||
/// .sink { print ("\($0)", terminator: " ") }
|
||||
/// // Prints: "0 1 3 6 10 15 ".
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The previous result returned by the `nextPartialResult`
|
||||
@@ -37,7 +42,37 @@ extension Publisher {
|
||||
/// Transforms elements from the upstream publisher by providing the current element
|
||||
/// to an error-throwing closure along with the last value returned by the closure.
|
||||
///
|
||||
/// Use `tryScan(_:_:)` to accumulate all previously-published values into a single
|
||||
/// value, which you then combine with each newly-published value.
|
||||
/// If your accumulator closure throws an error, the publisher terminates with
|
||||
/// the error.
|
||||
///
|
||||
/// In the example below, `tryScan(_:_:)` calls a division function on elements of
|
||||
/// a collection publisher. The `Publishers.TryScan` publisher publishes each result
|
||||
/// until the function encounters a `DivisionByZeroError`, which terminates
|
||||
/// the publisher.
|
||||
///
|
||||
/// struct DivisionByZeroError: Error {}
|
||||
///
|
||||
/// /// A function that throws a DivisionByZeroError if `current` provided by the
|
||||
/// /// TryScan publisher is zero.
|
||||
/// func myThrowingFunction(_ lastValue: Int, _ currentValue: Int) throws -> Int {
|
||||
/// guard currentValue != 0 else { throw DivisionByZeroError() }
|
||||
/// return (lastValue + currentValue) / currentValue
|
||||
/// }
|
||||
///
|
||||
/// let numbers = [1,2,3,4,5,0,6,7,8,9]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryScan(10) { try myThrowingFunction($0, $1) }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("\($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "11 6 3 1 1 -1 failure(DivisionByZeroError())".
|
||||
///
|
||||
/// If the closure throws an error, the publisher fails with the error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The previous result returned by the `nextPartialResult`
|
||||
/// closure.
|
||||
|
||||
@@ -29,13 +29,13 @@ extension Publishers {
|
||||
where Failure == Downstream.Failure,
|
||||
Elements.Element == Downstream.Input
|
||||
{
|
||||
var iterator = sequence.makeIterator()
|
||||
if iterator.next() != nil {
|
||||
let inner = Inner(downstream: subscriber, sequence: sequence)
|
||||
subscriber.receive(subscription: inner)
|
||||
} else {
|
||||
let inner = Inner(downstream: subscriber, sequence: sequence)
|
||||
if inner.isExhausted {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .finished)
|
||||
inner.cancel()
|
||||
} else {
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ extension Publishers.Sequence {
|
||||
where Downstream.Input == Elements.Element,
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
typealias Iterator = Elements.Iterator
|
||||
typealias Element = Elements.Element
|
||||
@@ -75,6 +74,10 @@ extension Publishers.Sequence {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
fileprivate var isExhausted: Bool {
|
||||
return next == nil
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return sequence.map(String.init(describing:)) ?? "Sequence"
|
||||
}
|
||||
|
||||
@@ -48,9 +48,30 @@ extension Publisher where Failure == Never {
|
||||
|
||||
/// Changes the failure type declared by the upstream publisher.
|
||||
///
|
||||
/// The publisher returned by this method cannot actually fail
|
||||
/// with the specified type and instead just finishes normally. Instead, you use
|
||||
/// this method when you need to match the error types of two mismatched publishers.
|
||||
/// Use `setFailureType(to:)` when you need set the error type of a publisher that
|
||||
/// cannot fail.
|
||||
///
|
||||
/// Conversely, if the upstream can fail, you would use `mapError(_:)` to provide
|
||||
/// instructions on converting the error types to needed by the downstream publisher’s
|
||||
/// inputs.
|
||||
///
|
||||
/// The following example has two publishers with mismatched error types: `pub1`’s
|
||||
/// error type is `Never`, and `pub2`’s error type is `Error`. Because of
|
||||
/// the mismatch, the `combineLatest(_:)` operator requires that `pub1` use
|
||||
/// `setFailureType(to:)` to make it appear that `pub1` can produce the `Error` type,
|
||||
/// like `pub2` can.
|
||||
///
|
||||
/// let pub1 = [0, 1, 2, 3, 4, 5].publisher
|
||||
/// let pub2 = CurrentValueSubject<Int, Error>(0)
|
||||
/// let cancellable = pub1
|
||||
/// .setFailureType(to: Error.self)
|
||||
/// .combineLatest(pub2)
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completed: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)")}
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "value: (5, 0)".
|
||||
///
|
||||
/// - Parameter failureType: The `Failure` type presented by this publisher.
|
||||
/// - Returns: A publisher that appears to send the specified failure type.
|
||||
|
||||
@@ -7,13 +7,53 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher as a class instance.
|
||||
/// Shares the output of an upstream publisher with multiple subscribers.
|
||||
///
|
||||
/// The downstream subscriber receives 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.
|
||||
/// The publisher returned by this operator supports multiple subscribers, all of whom
|
||||
/// receive unchanged elements and completion states from the upstream publisher.
|
||||
///
|
||||
/// - Returns: A class instance that republishes its upstream publisher.
|
||||
/// - Tip: `Publishers.Share` is effectively a combination of
|
||||
/// the `Publishers.Multicast` and `PassthroughSubject` publishers, with an implicit
|
||||
/// `autoconnect()`.
|
||||
///
|
||||
/// The following example uses a sequence publisher as a counter to publish three
|
||||
/// random numbers, generated by a `map(_:)` operator. It uses a `share()` operator
|
||||
/// to share the same random number to each of two subscribers. This example uses
|
||||
/// a `delay(for:tolerance:scheduler:options:)` operator only to prevent the first
|
||||
/// subscriber from exhausting the sequence publisher immediately; an asynchronous
|
||||
/// publisher wouldn’t need this.
|
||||
///
|
||||
/// let pub = (1...3).publisher
|
||||
/// .delay(for: 1, scheduler: DispatchQueue.main)
|
||||
/// .map( { _ in return Int.random(in: 0...100) } )
|
||||
/// .print("Random")
|
||||
/// .share()
|
||||
///
|
||||
/// cancellable1 = pub
|
||||
/// .sink { print ("Stream 1 received: \($0)")}
|
||||
/// cancellable2 = pub
|
||||
/// .sink { print ("Stream 2 received: \($0)")}
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Random: receive value: (20)
|
||||
/// // Stream 1 received: 20
|
||||
/// // Stream 2 received: 20
|
||||
/// // Random: receive value: (85)
|
||||
/// // Stream 1 received: 85
|
||||
/// // Stream 2 received: 85
|
||||
/// // Random: receive value: (98)
|
||||
/// // Stream 1 received: 98
|
||||
/// // Stream 2 received: 98
|
||||
///
|
||||
/// Without the `share()` operator, stream 1 receives three random values,
|
||||
/// followed by stream 2 receiving three different random values.
|
||||
///
|
||||
/// Also note that `Publishers.Share` is a `class` rather than a `struct` like most
|
||||
/// other publishers. This means you can use this operator to create a publisher
|
||||
/// instance that uses reference semantics.
|
||||
///
|
||||
/// - Returns: A class instance that shares elements received from its upstream to
|
||||
/// multiple subscribers.
|
||||
public func share() -> Publishers.Share<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
@@ -21,8 +61,19 @@ extension Publisher {
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher implemented as a class, which otherwise behaves like its upstream
|
||||
/// publisher.
|
||||
/// A publisher that shares the output of an upstream publisher with multiple
|
||||
/// subscribers.
|
||||
///
|
||||
/// This publisher type supports multiple subscribers, all of whom receive unchanged
|
||||
/// elements and completion states from the upstream publisher.
|
||||
///
|
||||
/// - Tip: `Publishers.Share` is effectively a combination of
|
||||
/// the `Publishers.Multicast` and `PassthroughSubject` publishers,
|
||||
/// with an implicit `autoconnect()`.
|
||||
///
|
||||
/// Also note that `Publishers.Share` is a `class` rather than a `struct` like most
|
||||
/// other publishers. Use this type when you need a publisher instance that uses
|
||||
/// reference semantics.
|
||||
public final class Share<Upstream: Publisher>: Publisher, Equatable {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
@@ -11,20 +11,27 @@ extension Publisher {
|
||||
/// operations.
|
||||
///
|
||||
/// In contrast with `receive(on:options:)`, which affects downstream messages,
|
||||
/// `subscribe(on:)` changes the execution context of upstream messages.
|
||||
/// In the following example, requests to `jsonPublisher` are performed on
|
||||
/// `backgroundQueue`, but elements received from it are performed on `RunLoop.main`.
|
||||
/// `subscribe(on:options:)` changes the execution context of upstream messages.
|
||||
///
|
||||
/// In the following example, the `subscribe(on:options:)` operator causes
|
||||
/// `ioPerformingPublisher` to receive requests on `backgroundQueue`, while
|
||||
/// the `receive(on:options:)` causes `uiUpdatingSubscriber` to receive elements and
|
||||
/// completion on `RunLoop.main`.
|
||||
///
|
||||
/// let ioPerformingPublisher == // Some publisher.
|
||||
/// let uiUpdatingSubscriber == // Some subscriber that updates the UI.
|
||||
///
|
||||
/// ioPerformingPublisher
|
||||
/// .subscribe(on: backgroundQueue)
|
||||
/// .receiveOn(on: RunLoop.main)
|
||||
/// .receive(on: RunLoop.main)
|
||||
/// .subscribe(uiUpdatingSubscriber)
|
||||
///
|
||||
///
|
||||
/// Using `subscribe(on:options:)` also causes the upstream publisher to perform
|
||||
/// `cancel()` using the specfied scheduler.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scheduler: The scheduler on which to receive upstream messages.
|
||||
/// - scheduler: The scheduler used to send messages to upstream publishers.
|
||||
/// - options: Options that customize the delivery of elements.
|
||||
/// - Returns: A publisher which performs upstream operations on the specified
|
||||
/// scheduler.
|
||||
@@ -67,8 +74,11 @@ extension Publishers {
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(scheduler: scheduler,
|
||||
options: options,
|
||||
downstream: subscriber)
|
||||
scheduler.schedule(options: options) {
|
||||
self.upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
self.upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,20 +97,19 @@ extension Publishers.SubscribeOn {
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
typealias SubscribeOn = Publishers.SubscribeOn<Upstream, Context>
|
||||
|
||||
private enum State {
|
||||
case ready(SubscribeOn, Downstream)
|
||||
case subscribed(SubscribeOn, Downstream, Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state: State
|
||||
private let downstream: Downstream
|
||||
private let scheduler: Context
|
||||
private let options: Context.SchedulerOptions?
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
private let upstreamLock = UnfairLock.allocate()
|
||||
|
||||
init(_ subscribeOn: SubscribeOn, downstream: Downstream) {
|
||||
state = .ready(subscribeOn, downstream)
|
||||
init(scheduler: Context,
|
||||
options: Context.SchedulerOptions?,
|
||||
downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -110,19 +119,19 @@ extension Publishers.SubscribeOn {
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(subscribeOn, downstream) = state else {
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscribeOn, downstream, subscription)
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
@@ -130,9 +139,9 @@ extension Publishers.SubscribeOn {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
@@ -143,13 +152,13 @@ extension Publishers.SubscribeOn {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscribeOn, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscribeOn.scheduler.schedule(options: subscribeOn.options) { [weak self] in
|
||||
self?.scheduledRequest(demand, subscription: subscription)
|
||||
scheduler.schedule(options: options) {
|
||||
self.scheduledRequest(demand, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,14 +171,14 @@ extension Publishers.SubscribeOn {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscribeOn, _, subscription) = state else {
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscribeOn.scheduler.schedule(options: subscribeOn.options) { [weak self] in
|
||||
self?.scheduledCancel(subscription)
|
||||
scheduler.schedule(options: options) {
|
||||
self.scheduledCancel(subscription)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,31 +7,144 @@
|
||||
|
||||
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.
|
||||
/// Republishes elements sent by the most recently received publisher.
|
||||
///
|
||||
/// 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.
|
||||
/// This operator works with an upstream publisher of publishers, flattening
|
||||
/// the stream of elements to appear as if they were coming from a single stream of
|
||||
/// elements. It switches the inner publisher as new ones arrive but keeps the outer
|
||||
/// publisher constant for downstream subscribers.
|
||||
///
|
||||
/// For example, given the type `AnyPublisher<URLSession.DataTaskPublisher, NSError>`,
|
||||
/// calling `switchToLatest()` results in the type
|
||||
/// `SwitchToLatest<(Data, URLResponse), URLError>`.
|
||||
/// The downstream subscriber sees a continuous stream of `(Data, URLResponse)`
|
||||
/// elements from what looks like a single `URLSession.DataTaskPublisher` even though
|
||||
/// the elements are coming from different upstream publishers.
|
||||
///
|
||||
/// When this operator receives a new publisher from the upstream publisher, it
|
||||
/// cancels its previous subscription. Use this feature to prevent earlier publishers
|
||||
/// from performing unnecessary work, such as creating network request publishers from
|
||||
/// frequently updating user interface publishers.
|
||||
///
|
||||
/// The following example updates a `PassthroughSubject` with a new value every
|
||||
/// `0.1` seconds. A `map(_:)` operator receives the new value and uses it to create
|
||||
/// a new `URLSession.DataTaskPublisher`. By using the `switchToLatest()` operator,
|
||||
/// the downstream sink subscriber receives the `(Data, URLResponse)` output type from
|
||||
/// the data task publishers, rather than the `URLSession.DataTaskPublisher` type
|
||||
/// produced by the `map(_:)` operator. Furthermore, creating each new data task
|
||||
/// publisher cancels the previous data task publisher.
|
||||
///
|
||||
/// let subject = PassthroughSubject<Int, Never>()
|
||||
/// cancellable = subject
|
||||
/// .setFailureType(to: URLError.self)
|
||||
/// .map() { index -> URLSession.DataTaskPublisher in
|
||||
/// let url = URL(string: "https://example.org/get?index=\(index)")!
|
||||
/// return URLSession.shared.dataTaskPublisher(for: url)
|
||||
/// }
|
||||
/// .switchToLatest()
|
||||
/// .sink(receiveCompletion: { print("Complete: \($0)") },
|
||||
/// receiveValue: { (data, response) in
|
||||
/// guard let url = response.url else {
|
||||
/// print("Bad response.")
|
||||
/// return
|
||||
/// }
|
||||
/// print("URL: \(url)")
|
||||
/// })
|
||||
///
|
||||
/// for index in 1...5 {
|
||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(index/10)) {
|
||||
/// subject.send(index)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Prints "URL: https://example.org/get?index=5"
|
||||
///
|
||||
/// The exact behavior of this example depends on the value of `asyncAfter` and
|
||||
/// the speed of the network connection. If the delay value is longer, or the network
|
||||
/// connection is fast, the earlier data tasks may complete before `switchToLatest()`
|
||||
/// can cancel them. If this happens, the output includes multiple URLs whose tasks
|
||||
/// complete before cancellation.
|
||||
public func switchToLatest() -> Publishers.SwitchToLatest<Output, Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Output: Publisher, Failure == Never {
|
||||
|
||||
/// Republishes elements sent by the most recently received publisher.
|
||||
///
|
||||
/// This operator works with an upstream publisher of publishers, flattening
|
||||
/// the stream of elements to appear as if they were coming from a single stream of
|
||||
/// elements. It switches the inner publisher as new ones arrive but keeps the outer
|
||||
/// publisher constant for downstream subscribers.
|
||||
///
|
||||
/// When this operator receives a new publisher from the upstream publisher, it
|
||||
/// cancels its previous subscription. Use this feature to prevent earlier publishers
|
||||
/// from performing unnecessary work, such as creating network request publishers from
|
||||
/// frequently updating user interface publishers.
|
||||
public func switchToLatest() -> Publishers.SwitchToLatest<
|
||||
Output, Publishers.SetFailureType<Self, Output.Failure>
|
||||
> {
|
||||
return setFailureType(to: Output.Failure.self).switchToLatest()
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Output: Publisher, Failure == Never, Output.Failure == Never {
|
||||
|
||||
/// Republishes elements sent by the most recently received publisher.
|
||||
///
|
||||
/// This operator works with an upstream publisher of publishers, flattening
|
||||
/// the stream of elements to appear as if they were coming from a single stream of
|
||||
/// elements. It switches the inner publisher as new ones arrive but keeps the outer
|
||||
/// publisher constant for downstream subscribers.
|
||||
///
|
||||
/// When this operator receives a new publisher from the upstream publisher, it
|
||||
/// cancels its previous subscription. Use this feature to prevent earlier publishers
|
||||
/// from performing unnecessary work, such as creating network request publishers from
|
||||
/// frequently updating user interface publishers.
|
||||
public func switchToLatest() -> Publishers.SwitchToLatest<Output, Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Output: Publisher, Output.Failure == Never {
|
||||
|
||||
/// Republishes elements sent by the most recently received publisher.
|
||||
///
|
||||
/// This operator works with an upstream publisher of publishers, flattening
|
||||
/// the stream of elements to appear as if they were coming from a single stream of
|
||||
/// elements. It switches the inner publisher as new ones arrive but keeps the outer
|
||||
/// publisher constant for downstream subscribers.
|
||||
///
|
||||
/// When this operator receives a new publisher from the upstream publisher, it
|
||||
/// cancels its previous subscription. Use this feature to prevent earlier publishers
|
||||
/// from performing unnecessary work, such as creating network request publishers from
|
||||
/// frequently updating user interface publishers.
|
||||
public func switchToLatest() -> Publishers.SwitchToLatest<
|
||||
Publishers.SetFailureType<Output, Failure>,
|
||||
Publishers.Map<Self, Publishers.SetFailureType<Output, Failure>>
|
||||
> {
|
||||
return map { $0.setFailureType(to: Failure.self) }.switchToLatest()
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that “flattens” nested 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.
|
||||
/// Given a publisher that publishes `Publisher` instances,
|
||||
/// the `Publishers.SwitchToLatest` publisher produces a sequence of events from only
|
||||
/// the most recent one. For example, given the type
|
||||
/// `AnyPublisher<URLSession.DataTaskPublisher, NSError>`, calling `switchToLatest()`
|
||||
/// results in the type `SwitchToLatest<(Data, URLResponse), URLError>`.
|
||||
/// The downstream subscriber sees a continuous stream of `(Data, URLResponse)`
|
||||
/// elements from what looks like a single `URLSession.DataTaskPublisher` even though
|
||||
/// the elements are coming from different upstream publishers.
|
||||
///
|
||||
/// 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.
|
||||
/// When `Publishers.SwitchToLatest` receives a new publisher from the upstream
|
||||
/// publisher, it cancels its previous subscription. Use this feature to prevent
|
||||
/// earlier publishers from performing unnecessary work, such as creating network
|
||||
/// request publishers from frequently-updating user interface publishers.
|
||||
public struct SwitchToLatest<NestedPublisher: Publisher, Upstream: Publisher>
|
||||
: Publisher
|
||||
where Upstream.Output == NestedPublisher,
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// Publishers.Throttle.swift
|
||||
//
|
||||
//
|
||||
// Created by Stuart Austin on 14/11/2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
// swiftlint:disable generic_type_name line_length
|
||||
|
||||
/// Publishes either the most-recent or first element published by the upstream
|
||||
/// publisher in the specified time interval.
|
||||
///
|
||||
/// Use `throttle(for:scheduler:latest:`` to selectively republish elements from
|
||||
/// an upstream publisher during an interval you specify. Other elements received from
|
||||
/// the upstream in the throttling interval aren’t republished.
|
||||
///
|
||||
/// In the example below, a `Timer.TimerPublisher` produces elements on 3-second
|
||||
/// intervals; the `throttle(for:scheduler:latest:)` operator delivers the first
|
||||
/// event, then republishes only the latest event in the following ten second
|
||||
/// intervals:
|
||||
///
|
||||
/// cancellable = Timer.publish(every: 3.0, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .print("\(Date().description)")
|
||||
/// .throttle(for: 10.0, scheduler: RunLoop.main, latest: true)
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("Completion: \($0).") },
|
||||
/// receiveValue: { print("Received Timestamp \($0).") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:26:57 +0000)
|
||||
/// // Received Timestamp 2020-03-19 18:26:57 +0000.
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:00 +0000)
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:03 +0000)
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:06 +0000)
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:09 +0000)
|
||||
/// // Received Timestamp 2020-03-19 18:27:09 +0000.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The interval at which to find and emit either the most recent or
|
||||
/// the first element, expressed in the time system of the scheduler.
|
||||
/// - scheduler: The scheduler on which to publish elements.
|
||||
/// - latest: A Boolean value that indicates whether to publish the most recent
|
||||
/// element. If `false`, the publisher emits the first element received during
|
||||
/// the interval.
|
||||
/// - Returns: A publisher that emits either the most-recent or first element received
|
||||
/// during the specified interval.
|
||||
public func throttle<S>(for interval: S.SchedulerTimeType.Stride,
|
||||
scheduler: S,
|
||||
latest: Bool) -> Publishers.Throttle<Self, S>
|
||||
where S: Scheduler
|
||||
{
|
||||
return .init(upstream: self,
|
||||
interval: interval,
|
||||
scheduler: scheduler,
|
||||
latest: latest)
|
||||
}
|
||||
|
||||
// swiftlint:enable generic_type_name line_length
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes either the most-recent or first element published by
|
||||
/// the upstream publisher in a specified time interval.
|
||||
public struct Throttle<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 interval in which to find and emit the most recent element.
|
||||
public let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler on which to publish elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// A Boolean value indicating whether to publish the most recent element.
|
||||
///
|
||||
/// If `false`, the publisher emits the first element received during
|
||||
/// the interval.
|
||||
public let latest: Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
latest: Bool) {
|
||||
|
||||
self.upstream = upstream
|
||||
self.interval = interval
|
||||
self.scheduler = scheduler
|
||||
self.latest = latest
|
||||
}
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// Implementations of ``Publisher`` must implement this method.
|
||||
///
|
||||
/// The provided implementation of ``Publisher/subscribe(_:)-4u8kn``calls
|
||||
/// this method.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to attach to this ``Publisher``,
|
||||
/// after which it can receive values.
|
||||
public func receive<S>(subscriber: S)
|
||||
where S: Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
{
|
||||
let inner = Inner(interval: interval,
|
||||
scheduler: scheduler,
|
||||
latest: latest,
|
||||
downstream: subscriber)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
|
||||
// swiftlint:enable generic_type_name
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Throttle {
|
||||
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 awaitingSubscription(Downstream)
|
||||
case subscribed(Subscription, Downstream)
|
||||
case pendingTerminal(Subscription, Downstream)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let interval: Context.SchedulerTimeType.Stride
|
||||
private let scheduler: Context
|
||||
private let latest: Bool
|
||||
private var state: State
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var lastEmissionTime: Context.SchedulerTimeType?
|
||||
|
||||
private var pendingInput: Input?
|
||||
private var pendingCompletion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var demand: Subscribers.Demand = .none
|
||||
|
||||
private var lastTime: Context.SchedulerTimeType
|
||||
|
||||
init(interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
latest: Bool,
|
||||
downstream: Downstream) {
|
||||
self.state = .awaitingSubscription(downstream)
|
||||
self.interval = interval
|
||||
self.scheduler = scheduler
|
||||
self.latest = latest
|
||||
|
||||
self.lastTime = scheduler.now
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .awaitingSubscription(downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.lastTime = scheduler.now
|
||||
|
||||
state = .subscribed(subscription, downstream)
|
||||
lock.unlock()
|
||||
|
||||
subscription.request(.unlimited)
|
||||
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
let lastTime = scheduler.now
|
||||
self.lastTime = lastTime
|
||||
|
||||
guard demand > .none else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
let hasScheduledOutput = (pendingInput != nil || pendingCompletion != nil)
|
||||
|
||||
if hasScheduledOutput && latest {
|
||||
pendingInput = input
|
||||
lock.unlock()
|
||||
} else if !hasScheduledOutput {
|
||||
let minimumEmissionTime =
|
||||
lastEmissionTime.map { $0.advanced(by: interval) }
|
||||
|
||||
let emissionTime =
|
||||
minimumEmissionTime.map { Swift.max(lastTime, $0) } ?? lastTime
|
||||
|
||||
demand -= 1
|
||||
|
||||
pendingInput = input
|
||||
lock.unlock()
|
||||
|
||||
let action: () -> Void = { [weak self] in
|
||||
self?.scheduledEmission()
|
||||
}
|
||||
|
||||
if emissionTime == lastTime {
|
||||
scheduler.schedule(action)
|
||||
} else {
|
||||
scheduler.schedule(after: emissionTime, action)
|
||||
}
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription, downstream) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let lastTime = scheduler.now
|
||||
self.lastTime = lastTime
|
||||
state = .pendingTerminal(subscription, downstream)
|
||||
|
||||
let hasScheduledOutput = (pendingInput != nil || pendingCompletion != nil)
|
||||
|
||||
if hasScheduledOutput && pendingCompletion == nil {
|
||||
pendingCompletion = completion
|
||||
lock.unlock()
|
||||
} else if !hasScheduledOutput {
|
||||
pendingCompletion = completion
|
||||
lock.unlock()
|
||||
|
||||
scheduler.schedule { [weak self] in
|
||||
self?.scheduledEmission()
|
||||
}
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledEmission() {
|
||||
lock.lock()
|
||||
|
||||
let downstream: Downstream
|
||||
|
||||
switch state {
|
||||
case .awaitingSubscription, .terminal:
|
||||
lock.unlock()
|
||||
return
|
||||
case let .subscribed(_, foundDownstream),
|
||||
let .pendingTerminal(_, foundDownstream):
|
||||
downstream = foundDownstream
|
||||
}
|
||||
|
||||
if self.pendingInput != nil && self.pendingCompletion == nil {
|
||||
lastEmissionTime = scheduler.now
|
||||
}
|
||||
|
||||
let pendingInput = self.pendingInput
|
||||
let pendingCompletion = self.pendingCompletion
|
||||
|
||||
self.pendingInput = nil
|
||||
self.pendingCompletion = nil
|
||||
|
||||
if pendingCompletion != nil {
|
||||
state = .terminal
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
|
||||
downstreamLock.lock()
|
||||
|
||||
let newDemand: Subscribers.Demand
|
||||
if let input = pendingInput {
|
||||
newDemand = downstream.receive(input)
|
||||
} else {
|
||||
newDemand = .none
|
||||
}
|
||||
|
||||
if let completion = pendingCompletion {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
downstreamLock.unlock()
|
||||
|
||||
guard newDemand > 0 else { return }
|
||||
self.lock.lock()
|
||||
demand += newDemand
|
||||
self.lock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard demand > 0 else { return }
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
|
||||
let subscription: Subscription?
|
||||
switch state {
|
||||
case let .subscribed(existingSubscription, _),
|
||||
let .pendingTerminal(existingSubscription, _):
|
||||
subscription = existingSubscription
|
||||
case .awaitingSubscription, .terminal:
|
||||
subscription = nil
|
||||
}
|
||||
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
|
||||
subscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Throttle" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,45 @@ extension Publisher {
|
||||
/// Terminates publishing if the upstream publisher exceeds the specified time
|
||||
/// interval without producing an element.
|
||||
///
|
||||
/// Use `timeout(_:scheduler:options:customError:)` to terminate a publisher if
|
||||
/// an element isn’t delivered within a timeout interval you specify.
|
||||
///
|
||||
/// In the example below, a `PassthroughSubject` publishes `String` elements and is
|
||||
/// configured to time out if no new elements are received within its `TIME_OUT`
|
||||
/// window of 5 seconds. A single value is published after the specified 2-second
|
||||
/// `WAIT_TIME`, after which no more elements are available; the publisher then times
|
||||
/// out and completes normally.
|
||||
///
|
||||
/// var WAIT_TIME : Int = 2
|
||||
/// var TIMEOUT_TIME : Int = 5
|
||||
///
|
||||
/// let subject = PassthroughSubject<String, Never>()
|
||||
/// let cancellable = subject
|
||||
/// .timeout(.seconds(TIMEOUT_TIME),
|
||||
/// scheduler: DispatchQueue.main,
|
||||
/// options: nil,
|
||||
/// customError: nil)
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0) at \(Date())") },
|
||||
/// receiveValue: { print ("value: \($0) at \(Date())") }
|
||||
/// )
|
||||
///
|
||||
/// DispatchQueue.main.asyncAfter(
|
||||
/// deadline: .now() + .seconds(WAIT_TIME),
|
||||
/// execute: {
|
||||
/// subject.send("Some data - sent after a delay of \(WAIT_TIME) seconds")
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// // Prints:
|
||||
/// // value: Some data - sent after a delay of 2 seconds at
|
||||
/// // 2020-03-10 23:47:59 +0000
|
||||
/// // completion: finished at 2020-03-10 23:48:04 +0000
|
||||
///
|
||||
/// If `customError` is `nil`, the publisher completes normally; if you provide
|
||||
/// a closure for the `customError` argument, the upstream publisher is instead
|
||||
/// terminated upon timeout, and the error is delivered to the downstream.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The maximum time interval the publisher can go without emitting
|
||||
/// an element, expressed in the time system of the scheduler.
|
||||
@@ -24,7 +63,7 @@ extension Publisher {
|
||||
_ interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil,
|
||||
customError: (() -> Self.Failure)? = nil
|
||||
customError: (() -> Failure)? = nil
|
||||
) -> Publishers.Timeout<Self, Context> {
|
||||
return .init(upstream: self,
|
||||
interval: interval,
|
||||
@@ -110,6 +149,8 @@ extension Publishers.Timeout {
|
||||
|
||||
private var timer: AnyCancellable?
|
||||
|
||||
private var initialDemand = false
|
||||
|
||||
init(downstream: Downstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
@@ -140,10 +181,9 @@ extension Publishers.Timeout {
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard !didTimeout, case .subscribed = state else {
|
||||
lock.unlock()
|
||||
@@ -154,22 +194,21 @@ extension Publishers.Timeout {
|
||||
timer = timeoutClock()
|
||||
lock.unlock()
|
||||
scheduler.schedule(options: options) {
|
||||
self.downstreamLock.lock()
|
||||
_ = self.downstream.receive(input)
|
||||
self.downstreamLock.unlock()
|
||||
self.scheduledReceive(input)
|
||||
}
|
||||
return .unlimited
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
timer?.cancel()
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
scheduler.schedule(options: options) {
|
||||
self.downstreamLock.lock()
|
||||
self.downstream.receive(completion: completion)
|
||||
self.downstreamLock.unlock()
|
||||
self.scheduledReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +218,10 @@ extension Publishers.Timeout {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if !initialDemand {
|
||||
timer = timeoutClock()
|
||||
initialDemand = true
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
@@ -191,6 +234,7 @@ extension Publishers.Timeout {
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
timer?.cancel()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
@@ -222,8 +266,36 @@ extension Publishers.Timeout {
|
||||
interval: interval,
|
||||
tolerance: scheduler.minimumTolerance,
|
||||
options: options,
|
||||
{ [weak self] in self?.timedOut() })
|
||||
return AnyCancellable { cancellable.cancel() }
|
||||
timedOut)
|
||||
return AnyCancellable(cancellable.cancel)
|
||||
}
|
||||
|
||||
private func scheduledReceive(_ input: Input) {
|
||||
lock.lock()
|
||||
guard !didTimeout, case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
if newDemand != .none {
|
||||
subscription.request(newDemand)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user