Compare commits
173 Commits
0.2.0
...
merge-publisher
| Author | SHA1 | Date | |
|---|---|---|---|
| af0ae86407 | |||
| f861335dc3 | |||
| 769c3c818f | |||
| 910d21da4c | |||
| 6e20956d6d | |||
| e453879d75 | |||
| 98f6b6b337 | |||
| 74b739d74e | |||
| bcba9a19d4 | |||
| 486e166462 | |||
| c6536cf8d3 | |||
| cf41c25cf7 | |||
| b4557fb311 | |||
| f8e6e66ab4 | |||
| 95b42abce3 | |||
| 899a04bb3f | |||
| 5f9a700689 | |||
| a300fd09d3 | |||
| 5973f86c6e | |||
| 1b5afdba26 | |||
| 51d5d1e71d | |||
| a8bc5cc046 | |||
| 86d6170dc9 | |||
| 171131d768 | |||
| d6b4fb4115 | |||
| 014b82b99d | |||
| 7c5a76cf2b | |||
| 668c292245 | |||
| 981ab4fa09 | |||
| 8cf71e0122 | |||
| 130125cb66 | |||
| 7b3ceae666 | |||
| 56202b1663 | |||
| d0a02de7c5 | |||
| c8058edc5f | |||
| 8b685f78a3 | |||
| 0816abe33c | |||
| b6c7560f4c | |||
| 7a9e8b22d2 | |||
| 26f9acd75a | |||
| 4c42b434ca | |||
| 8afe945325 | |||
| eb879213ef | |||
| e402fb3980 | |||
| 4f96378c02 | |||
| 243f3d200e | |||
| f1fb5552b5 | |||
| bfd875ccba | |||
| 27da28f378 | |||
| cba3a69e74 | |||
| d634a76c39 | |||
| b5e31a43ef | |||
| 1bb3583a36 | |||
| 449b8eef48 | |||
| d4bdd83a00 | |||
| f1cc94adff | |||
| 5e1e10a780 | |||
| 5b7358111c | |||
| 5f90c4c85f | |||
| 0dec30fcad | |||
| a5ec9723e2 | |||
| 25ac4dfa5f | |||
| f5645f605b | |||
| ffef3ac76c | |||
| 36a9f1999c | |||
| f069f9b9fa | |||
| 55bdbba0f9 | |||
| 1290545c49 | |||
| aed074af43 | |||
| 11ec7c89e6 | |||
| 46007658a1 | |||
| c4c7f2172d | |||
| 5b0a21a0b9 | |||
| f4e191b2ff | |||
| c275e51cdc | |||
| a84105133c | |||
| ef3ebd965a | |||
| a08b99c886 | |||
| 3398499540 | |||
| 1bf193ddaa | |||
| 3a88dfd76b | |||
| bd0b69d7cb | |||
| dba76c3c41 | |||
| 5863492753 | |||
| 2f2e16ee1f | |||
| bca131c2a4 | |||
| e999fafdce | |||
| b38830e0f1 | |||
| 525405f64d | |||
| 693d1145f8 | |||
| 2f9ddc2229 | |||
| bcd1b727f8 | |||
| 5d1034fcc0 | |||
| 2378f3d97e | |||
| 4a965830e7 | |||
| 9eabadb7c9 | |||
| dcfaec2c9d | |||
| 219ee38119 | |||
| 3a5389d398 | |||
| 69ead1c8fb | |||
| 8e6404592e | |||
| 14b7ced2fe | |||
| d7b9e87f6d | |||
| 4fd04b8a00 | |||
| 5f92ee05d2 | |||
| bdd703abb3 | |||
| e41c48a5cd | |||
| df0b8b08db | |||
| 7056143b99 | |||
| 0a965ba60a | |||
| 7dfaa4edea | |||
| 3e8f2774a4 | |||
| 68e9bbe164 | |||
| 0f71c33d72 | |||
| 3f61648f82 | |||
| c621ceb267 | |||
| 2aa297ec39 | |||
| 9cb27bb91b | |||
| d74f68da86 | |||
| f68dcd520f | |||
| 432fd4f48f | |||
| 9c6bbda0c4 | |||
| 3990ec2afb | |||
| 39dd9e40bf | |||
| fd7c0459b9 | |||
| f7145e7fa5 | |||
| ecd4766129 | |||
| e00a6f06fc | |||
| 23ee3a4b7b | |||
| 9c913124eb | |||
| 7ddd15b334 | |||
| 72753ef93c | |||
| 816426b48c | |||
| 47fb390081 | |||
| 1d3327f6bf | |||
| eb7478d430 | |||
| f69621f0e2 | |||
| 7f3cccf1ae | |||
| ec037dbb3d | |||
| 8a39f35d3f | |||
| 7fb92bffc6 | |||
| e441ea3048 | |||
| 22f7b6d10d | |||
| 7431d21c9c | |||
| 1d901fca7f | |||
| 9834eab0ea | |||
| 1ce9660ce9 | |||
| 313d6befa6 | |||
| 8c7f061892 | |||
| 2ac2470579 | |||
| 57c9ae8590 | |||
| d57c878651 | |||
| 7fa91778c2 | |||
| d15e604764 | |||
| 07c7a98d72 | |||
| 01ef05be1f | |||
| beee9d0d51 | |||
| aacd1a326c | |||
| 5528adcc67 | |||
| 1b810d0536 | |||
| 8b25238154 | |||
| 9b9915bde7 | |||
| 27f01e5f21 | |||
| 739eb47409 | |||
| 14d5a90e89 | |||
| 0e869bc861 | |||
| 2f38069166 | |||
| 97d07d0a14 | |||
| d3888a3808 | |||
| d2b8709afb | |||
| a28177e9c5 | |||
| cef19fce4b | |||
| 7f6bba62de |
@@ -0,0 +1,274 @@
|
||||
version: 2
|
||||
jobs:
|
||||
"Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
macos:
|
||||
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.3 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1.3"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-compatibility-xcodeproj
|
||||
- run:
|
||||
name: Building for testing on iOS 13.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing against Combine on iOS 13.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
- store_artifacts:
|
||||
path: xcodebuild_test-without-building.log
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
|
||||
"Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)":
|
||||
macos:
|
||||
xcode: "10.2.1"
|
||||
environment:
|
||||
BUNDLE_PATH: .bundle # path to install gems and use for caching
|
||||
SWIFT_VERSION: "5.0.1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Installing gem dependencies
|
||||
command: bundle install && bundle clean
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-simulator-cache-{{ arch }}
|
||||
- run:
|
||||
# CircleCI doesn't have an iOS 9 simulator, so we need to install it manually.
|
||||
name: Installing iOS 9 simulator
|
||||
command: |
|
||||
bundle exec xcversion simulators --install="iOS 9.3"
|
||||
bundle exec xcversion simulators
|
||||
xcrun simctl list
|
||||
- save_cache:
|
||||
key: v1-simulator-cache-{{ arch }}
|
||||
paths:
|
||||
- ~/Library/Caches/XcodeInstall
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: |
|
||||
make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
xcodebuild -scheme OpenCombine-Package -showdestinations
|
||||
- run:
|
||||
name: Building for testing on iOS 9.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing on iOS 9.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
|
||||
-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 tests on Ubuntu 18.04 (Swift 5.1.1)":
|
||||
docker:
|
||||
- image: swift:5.1.1-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1.1"
|
||||
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)
|
||||
|
||||
"Run SwiftLint and Danger":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install SwiftLint
|
||||
command: |
|
||||
brew install swiftlint
|
||||
- run:
|
||||
name: Install danger-swift
|
||||
command: |
|
||||
brew install danger/tap/danger-swift
|
||||
- run:
|
||||
name: Run danger-swift
|
||||
command: danger-swift ci
|
||||
|
||||
"Run Pod spec lint":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Pod lib lint
|
||||
command: |
|
||||
pod lib lint --allow-warnings --verbose
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
"OpenCombine: execute tests on macOS":
|
||||
jobs:
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)"
|
||||
"OpenCombine: execute compatibility tests":
|
||||
jobs:
|
||||
- "Execute compatibility tests on iOS 13.3 (Xcode 11.3.0, Swift 5.1.3)"
|
||||
"OpenCombine: execute tests on iOS":
|
||||
jobs:
|
||||
- "Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)"
|
||||
"OpenCombine: execute tests on Linux":
|
||||
jobs:
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.1.1)"
|
||||
"OpenCombine: run SwiftLint and Danger":
|
||||
jobs:
|
||||
- "Run SwiftLint and Danger"
|
||||
"OpenCombine: validate podspec files":
|
||||
jobs:
|
||||
- "Run Pod spec lint"
|
||||
@@ -0,0 +1,3 @@
|
||||
*.swift.gyb linguist-language=Swift
|
||||
|
||||
**/GENERATED-* linguist-generated=true
|
||||
+114
@@ -2,6 +2,7 @@
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
/.swiftpm
|
||||
|
||||
# Created by https://www.gitignore.io/api/Xcode
|
||||
# Edit at https://www.gitignore.io/?templates=Xcode
|
||||
@@ -42,3 +43,116 @@ DerivedData/
|
||||
# End of https://www.gitignore.io/api/Xcode
|
||||
|
||||
.idea
|
||||
|
||||
# Created by https://www.gitignore.io/api/Python
|
||||
# Edit at https://www.gitignore.io/?templates=Python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# End of https://www.gitignore.io/api/Python
|
||||
|
||||
.bundle/
|
||||
node_modules/
|
||||
|
||||
+34
-1
@@ -16,6 +16,8 @@ disabled_rules:
|
||||
- identifier_name
|
||||
- nesting
|
||||
- notification_center_detachment
|
||||
- no_fallthrough_only
|
||||
- no_space_in_method_call
|
||||
- redundant_string_enum_value
|
||||
- todo
|
||||
- trailing_comma
|
||||
@@ -59,7 +61,6 @@ opt_in_rules:
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- untyped_error_in_catch
|
||||
- unused_import
|
||||
- unused_private_declaration
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- yoda_condition
|
||||
@@ -75,3 +76,35 @@ generic_type_name:
|
||||
attributes:
|
||||
always_on_line_above:
|
||||
- "@usableFromInline"
|
||||
|
||||
custom_rules:
|
||||
no_foundation_dependency:
|
||||
included: Sources/OpenCombine/
|
||||
name: "No Foundation Dependency"
|
||||
regex: "^import.*Foundation.*$"
|
||||
message: "We don't want to depend on Foundation"
|
||||
severity: error
|
||||
|
||||
no_dispatch_dependency:
|
||||
included: Sources/OpenCombine/
|
||||
name: "No Dispatch Dependency"
|
||||
regex: "^import.*Dispatch.*$"
|
||||
message: "We don't want to depend on Dispatch"
|
||||
severity: error
|
||||
|
||||
inheritance_colon:
|
||||
name: "Inheritance Colon"
|
||||
regex: '\s[A-Z_]\w*(<[\w\s:\.,]+>)?(?: +:\s*|:(?:\s{0}|\s{2,}))([\[|\(]*\S)'
|
||||
message: "Colons should be next to the identifier of the inheriting type"
|
||||
severity: warning
|
||||
match_kinds:
|
||||
- identifier
|
||||
- typeidentifier
|
||||
|
||||
dictionary_type_colon:
|
||||
name: "Dictionary Type Colon"
|
||||
regex: '\[\w+(?:(?:\s{0}|\s{2,}):| :(?:\s{0}|\s{2,})\w)'
|
||||
message: "Colon should be surrounded by a single whitespace in a dictionary literal"
|
||||
severity: warning
|
||||
match_kinds:
|
||||
- typeidentifier
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,102 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombine"
|
||||
BuildableName = "OpenCombine"
|
||||
BlueprintName = "OpenCombine"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombineTests"
|
||||
BuildableName = "OpenCombineTests"
|
||||
BlueprintName = "OpenCombineTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
enableThreadSanitizer = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombine"
|
||||
BuildableName = "OpenCombine"
|
||||
BlueprintName = "OpenCombine"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombineTests"
|
||||
BuildableName = "OpenCombineTests"
|
||||
BlueprintName = "OpenCombineTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombine"
|
||||
BuildableName = "OpenCombine"
|
||||
BlueprintName = "OpenCombine"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
language: generic
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- swiftlint
|
||||
update: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- name: "Ubuntu 16.04 | Swift 5.1 | Tests"
|
||||
os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | Tests"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES" OPENCOMBINE_TEST="YES"
|
||||
# - name: "iOS 13.0 | Swift 5.1 | Compatibility Tests"
|
||||
# os: osx
|
||||
# osx_image: xcode11
|
||||
# env: SWIFT_VERSION="5.1" OPENCOMBINE_COMPATIBILITY_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | SwiftLint"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: SWIFT_VERSION="5.0" SWIFT_LINT="YES"
|
||||
before_install:
|
||||
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
cat /proc/cpuinfo;
|
||||
fi
|
||||
install:
|
||||
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
|
||||
fi
|
||||
- if [[ $CODE_COVERAGE == "YES" ]]; then
|
||||
gem install xcpretty;
|
||||
fi
|
||||
script:
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
swift test -c debug --enable-code-coverage --sanitize thread;
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
swift test -c release;
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
|
||||
swift package generate-xcodeproj --xcconfig-overrides iOS-Combine-Compatibility.xcconfig;
|
||||
set -o pipefail && xcodebuild -scheme OpenCombine-Package -sdk iphonesimulator13.0 -destination "platform=iOS Simulator,name=iPhone Xs,OS=13.0" build test | xcpretty;
|
||||
fi
|
||||
- if [[ $SWIFT_LINT == "YES" ]]; then
|
||||
swiftlint lint --strict --reporter "emoji";
|
||||
fi
|
||||
after_success:
|
||||
- if [[ $CODE_COVERAGE == "YES" ]]; then
|
||||
swift package generate-xcodeproj --enable-code-coverage;
|
||||
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
|
||||
bash <(curl -s https://codecov.io/bash);
|
||||
fi
|
||||
@@ -0,0 +1,76 @@
|
||||
import Danger
|
||||
import Foundation
|
||||
|
||||
extension StringProtocol {
|
||||
func dropSuffix<S: StringProtocol>(_ suffix: S) -> SubSequence {
|
||||
if hasSuffix(suffix) {
|
||||
return self[..<index(endIndex, offsetBy: -suffix.count)]
|
||||
} else {
|
||||
return self[...]
|
||||
}
|
||||
}
|
||||
|
||||
func directoryAndFileName() -> (SubSequence, SubSequence) {
|
||||
let lastPathSeparator = lastIndex(of: "/")
|
||||
if let lastPathSeparator = lastPathSeparator {
|
||||
return (self[..<lastPathSeparator], self[index(after: lastPathSeparator)...])
|
||||
} else {
|
||||
return (".", self[...])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let danger = Danger()
|
||||
|
||||
let allCreatedAndModified = danger.git.createdFiles + danger.git.modifiedFiles
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a GYB template but forgot to run `make gyb`.
|
||||
|
||||
let modifiedTemplates = allCreatedAndModified.filter { $0.hasSuffix(".gyb") }
|
||||
|
||||
for modifiedTemplate in modifiedTemplates {
|
||||
let (directory, filename) = modifiedTemplate.directoryAndFileName()
|
||||
let generated = "\(directory)/GENERATED-\(filename.dropSuffix(".gyb"))"
|
||||
|
||||
if !allCreatedAndModified.contains(generated) {
|
||||
fail("""
|
||||
A template \(modifiedTemplate) was modified, but the file \(generated) \
|
||||
was not regenerated.
|
||||
|
||||
Run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a generated file.
|
||||
// A template should be modified instead.
|
||||
|
||||
for modifiedGeneratedFile in danger.git.modifiedFiles
|
||||
where modifiedGeneratedFile.contains("GENERATED-")
|
||||
{
|
||||
let template = modifiedGeneratedFile
|
||||
.replacingOccurrences(of: "GENERATED-", with: "") + ".gyb"
|
||||
|
||||
if !danger.git.modifiedFiles.contains(template) {
|
||||
fail("""
|
||||
A generated file \(modifiedGeneratedFile) was modified, but \
|
||||
the template it was generated from was not modified.
|
||||
|
||||
Please modify the template \(template) instead, \
|
||||
run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwiftLint.lint(inline: true,
|
||||
configFile: ".swiftlint.yml",
|
||||
strict: true,
|
||||
lintAllFiles: true)
|
||||
|
||||
if danger.warnings.isEmpty, danger.fails.isEmpty {
|
||||
markdown("LGTM")
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.3)
|
||||
claide (1.0.3)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.4.1)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
emoji_regex (1.0.1)
|
||||
excon (0.71.0)
|
||||
faraday (0.17.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.7)
|
||||
fastlane (2.134.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 2.0)
|
||||
excon (>= 0.45.0, < 1.0.0)
|
||||
faraday (~> 0.17)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.13.1)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.21.2, < 0.24.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
jwt (~> 2.1.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_xml (~> 0.5)
|
||||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 2.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.8.1, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.23.9)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mime-types (~> 3.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.9)
|
||||
google-cloud-core (1.3.2)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.2.1)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-storage (1.16.0)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.23)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (>= 0.6.2, < 0.10.0)
|
||||
googleauth (0.6.7)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
json (2.2.0)
|
||||
jwt (2.1.0)
|
||||
memoist (0.16.1)
|
||||
mime-types (3.3)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.1009)
|
||||
mini_magick (4.9.5)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.2.6)
|
||||
naturally (2.2.0)
|
||||
os (1.0.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (2.0.5)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
rubyzip (1.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.11.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.6)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
tty-cursor (0.7.0)
|
||||
tty-screen (0.7.0)
|
||||
tty-spinner (0.9.1)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcode-install (2.6.2)
|
||||
claide (>= 0.9.1, < 1.1.0)
|
||||
fastlane (>= 2.1.0, < 3.0.0)
|
||||
xcodeproj (1.13.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.6)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
xcode-install
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.1
|
||||
@@ -0,0 +1,47 @@
|
||||
SWIFT_EXE=swift
|
||||
SWIFT_TEST_FLAGS=
|
||||
SWIFT_BUILD_FLAGS=-Xcc -Wunguarded-availability
|
||||
|
||||
debug:
|
||||
$(SWIFT_EXE) build -c debug $(SWIFT_BUILD_FLAGS)
|
||||
|
||||
release:
|
||||
$(SWIFT_EXE) build -c release $(SWIFT_BUILD_FLAGS)
|
||||
|
||||
test-debug:
|
||||
$(SWIFT_EXE) test -c debug $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
|
||||
|
||||
test-debug-sanitize-thread:
|
||||
$(SWIFT_EXE) test -c debug --sanitize thread $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
|
||||
|
||||
test-release:
|
||||
$(SWIFT_EXE) test -c release $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
|
||||
|
||||
swift-version:
|
||||
$(SWIFT_EXE) -version
|
||||
|
||||
test-compatibility:
|
||||
$(SWIFT_EXE) test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
|
||||
generate-compatibility-xcodeproj:
|
||||
$(SWIFT_EXE) package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
|
||||
open OpenCombine.xcodeproj
|
||||
|
||||
generate-xcodeproj:
|
||||
$(SWIFT_EXE) package $(SWIFT_BUILD_FLAGS) generate-xcodeproj --enable-code-coverage
|
||||
|
||||
gyb:
|
||||
$(shell ./utils/recursively_gyb.sh)
|
||||
|
||||
clean:
|
||||
rm -rf .build
|
||||
|
||||
.PHONY: debug release \
|
||||
test-debug \
|
||||
test-release \
|
||||
swift-version \
|
||||
test-compatibility-debug \
|
||||
generate-compatibility-xcodeproj \
|
||||
generate-xcodeproj \
|
||||
gyb \
|
||||
clean
|
||||
@@ -0,0 +1,27 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombine"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "Open source implementation of Apple's Combine framework for processing values over time."
|
||||
|
||||
spec.description = <<-DESC
|
||||
An open source implementation of Apple's Combine framework for processing values over time.
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
spec.osx.deployment_target = "10.10"
|
||||
spec.ios.deployment_target = "8.0"
|
||||
spec.watchos.deployment_target = "2.0"
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/COpenCombineHelpers/**/*.{h,cpp}", "Sources/OpenCombine/**/*.swift"
|
||||
spec.public_header_files = "Sources/COpenCombineHelpers/include/*.h"
|
||||
|
||||
spec.libraries = "c++"
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineDispatch"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "OpenCombine + Dispatch interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
Extends `DispatchQueue` with conformance to the `Scheduler` protocol
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
spec.osx.deployment_target = "10.10"
|
||||
spec.ios.deployment_target = "8.0"
|
||||
spec.watchos.deployment_target = "2.0"
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineDispatch/**/*.swift"
|
||||
spec.dependency "OpenCombine", '>= 0.6'
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineFoundation"
|
||||
spec.version = "0.7.0"
|
||||
spec.summary = "OpenCombine + OpenCombineFoundation interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
Adds publishers to Foundation types like NotificationCenter, URLSession etc.
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
spec.osx.deployment_target = "10.10"
|
||||
spec.ios.deployment_target = "8.0"
|
||||
spec.watchos.deployment_target = "2.0"
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineFoundation/**/*.swift"
|
||||
spec.dependency "OpenCombine", '~> 0.6'
|
||||
end
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "GottaGoFast",
|
||||
"repositoryURL": "https://github.com/broadwaylamb/GottaGoFast.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "85e1f5104fb1ede87d6f31acc0445555007c474c",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Yams",
|
||||
"repositoryURL": "https://github.com/jpsim/Yams.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+13
-6
@@ -6,13 +6,20 @@ let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/broadwaylamb/GottaGoFast.git", from: "0.1.0")
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "OpenCombine"),
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine", "GottaGoFast"])
|
||||
]
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# OpenCombine
|
||||
[](https://travis-ci.org/broadwaylamb/OpenCombine)
|
||||
[](https://circleci.com/gh/broadwaylamb/OpenCombine/tree/master)
|
||||
[](https://codecov.io/gh/broadwaylamb/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.
|
||||
|
||||
@@ -10,6 +12,42 @@ The main goal of this project is to provide a compatible, reliable and efficient
|
||||
|
||||
The project is in early development.
|
||||
|
||||
### Installation
|
||||
`OpenCombine` contains two public targets: `OpenCombine` and `OpenCombineDispatch` (the third one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
|
||||
|
||||
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`.
|
||||
|
||||
##### 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.
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/broadwaylamb/OpenCombine.git", from: "0.7.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine", "OpenCombineDispatch"])
|
||||
]
|
||||
```
|
||||
|
||||
###### Xcode
|
||||
`OpenCombine` can also be added as a SPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
|
||||
|
||||
To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Dependency…**, enter the [repository URL](https://github.com/broadwaylamb/OpenCombine.git), choose the latest available version, and activate the checkboxes:
|
||||
|
||||
<p align="center">
|
||||
<img alt="Select the OpenCombine and OpenCombineDispatch targets"
|
||||
src="https://user-images.githubusercontent.com/16309982/67618468-bd379f80-f7f8-11e9-917f-e76e878a1aee.png" width="70%">
|
||||
</p>
|
||||
|
||||
##### CocoaPods
|
||||
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.7'
|
||||
pod 'OpenCombineDispatch', '~> 0.7'
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
|
||||
@@ -21,9 +59,30 @@ You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff
|
||||
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
|
||||
|
||||
```
|
||||
$ swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
$ make test-compatibility
|
||||
```
|
||||
|
||||
Or enable the `-DOPENCOMBINE_COMPATIBILITY_TEST` compiler flag in Xcode's build settings. Note that on iOS only the latter will work.
|
||||
|
||||
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/broadwaylamb/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
|
||||
|
||||
#### GYB
|
||||
|
||||
Some publishers in OpenCombine (like `Publishers.MapKeyPath`, `Publishers.Merge`) exist in several
|
||||
different flavors in order to support several arities. For example, there are also `Publishers.MapKeyPath2`
|
||||
and `Publishers.MapKeyPath3`, which are very similar but different enough that Swift's type system
|
||||
can't help us here (because there's no support for variadic generics). Maintaining multiple instances of
|
||||
those generic types is tedious and error-prone (they can get out of sync), so we use the GYB tool for
|
||||
generating those instances from a template.
|
||||
|
||||
GYB is a Python script that evaluates Python code written inside a template file, so it's very flexible —
|
||||
templates can be arbitrarily complex. There is a good article about GYB on
|
||||
[NSHipster](https://nshipster.com/swift-gyb/).
|
||||
|
||||
GYB is part of the [Swift Open Source Project](https://github.com/apple/swift/blob/master/utils/gyb.py)
|
||||
and can be distributed under the same license as Swift itself.
|
||||
|
||||
GYB template files have the `.gyb` extension. Run `make gyb` to generate Swift code from those
|
||||
templates. The generated files are prefixed with `GENERATED-` and are checked into source control. Those
|
||||
files should never be edited directly. Instead, the `.gyb` template should be edited, and after that the files
|
||||
should be regenerated using `make gyb`.
|
||||
|
||||
+24
-2926
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
// From /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/swift/Foundation.swiftmodule/x86_64.swiftinterface
|
||||
// swift-interface-format-version: 1.0
|
||||
// swift-compiler-version: Apple Swift version 5.1.1 (swiftlang-1100.8.275.1 clang-1100.0.32.1)
|
||||
// swift-module-flags: -target x86_64-apple-macosx10.15 -enable-objc-interop -autolink-force-load -enable-library-evolution -module-link-name swiftFoundation -swift-version 5 -O -enforce-exclusivity=unchecked -module-name Foundation
|
||||
|
||||
public typealias Published = Combine.Published
|
||||
|
||||
public typealias ObservableObject = Combine.ObservableObject
|
||||
|
||||
public protocol _KeyValueCodingAndObservingPublishing {
|
||||
}
|
||||
|
||||
extension NSObject : Foundation._KeyValueCodingAndObservingPublishing {
|
||||
}
|
||||
|
||||
extension _KeyValueCodingAndObservingPublishing where Self : ObjectiveC.NSObject {
|
||||
public func publisher<Value>(for keyPath: Swift.KeyPath<Self, Value>, options: Foundation.NSKeyValueObservingOptions = [.initial, .new]) -> ObjectiveC.NSObject.KeyValueObservingPublisher<Self, Value>
|
||||
}
|
||||
|
||||
extension NSObject.KeyValueObservingPublisher {
|
||||
public func didChange() -> Combine.Publishers.Map<ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, Swift.Void>
|
||||
}
|
||||
|
||||
extension NSObject {
|
||||
public struct KeyValueObservingPublisher<Subject, Value> : Swift.Equatable where Subject : ObjectiveC.NSObject {
|
||||
public let object: Subject
|
||||
public let keyPath: Swift.KeyPath<Subject, Value>
|
||||
public let options: Foundation.NSKeyValueObservingOptions
|
||||
public init(object: Subject, keyPath: Swift.KeyPath<Subject, Value>, options: Foundation.NSKeyValueObservingOptions)
|
||||
public static func == (lhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, rhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>) -> Swift.Bool
|
||||
}
|
||||
}
|
||||
|
||||
extension NSObject.KeyValueObservingPublisher : Combine.Publisher {
|
||||
public typealias Output = Value
|
||||
public typealias Failure = Swift.Never
|
||||
public func receive<S>(subscriber: S) where Value == S.Input, S : Combine.Subscriber, S.Failure == ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>.Failure
|
||||
}
|
||||
|
||||
extension Timer {
|
||||
public static func publish(every interval: Foundation.TimeInterval, tolerance: Foundation.TimeInterval? = nil, on runLoop: Foundation.RunLoop, in mode: Foundation.RunLoop.Mode, options: Foundation.RunLoop.SchedulerOptions? = nil) -> Foundation.Timer.TimerPublisher
|
||||
final public class TimerPublisher : Combine.ConnectablePublisher {
|
||||
public typealias Output = Foundation.Date
|
||||
public typealias Failure = Swift.Never
|
||||
final public let interval: Foundation.TimeInterval
|
||||
final public let tolerance: Foundation.TimeInterval?
|
||||
final public let runLoop: Foundation.RunLoop
|
||||
final public let mode: Foundation.RunLoop.Mode
|
||||
final public let options: Foundation.RunLoop.SchedulerOptions?
|
||||
public init(interval: Foundation.TimeInterval, tolerance: Foundation.TimeInterval? = nil, runLoop: Foundation.RunLoop, mode: Foundation.RunLoop.Mode, options: Foundation.RunLoop.SchedulerOptions? = nil)
|
||||
final public func receive<S>(subscriber: S) where S : Combine.Subscriber, S.Failure == Foundation.Timer.TimerPublisher.Failure, S.Input == Foundation.Timer.TimerPublisher.Output
|
||||
final public func connect() -> Combine.Cancellable
|
||||
@objc deinit
|
||||
}
|
||||
}
|
||||
|
||||
extension OperationQueue : Combine.Scheduler {
|
||||
public struct SchedulerTimeType : Swift.Strideable, Swift.Codable, Swift.Hashable {
|
||||
public var date: Foundation.Date
|
||||
public init(_ date: Foundation.Date)
|
||||
public func distance(to other: Foundation.OperationQueue.SchedulerTimeType) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public func advanced(by n: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType
|
||||
public struct Stride : Swift.ExpressibleByFloatLiteral, Swift.Comparable, Swift.SignedNumeric, Swift.Codable, Combine.SchedulerTimeIntervalConvertible {
|
||||
public typealias FloatLiteralType = Foundation.TimeInterval
|
||||
public typealias IntegerLiteralType = Foundation.TimeInterval
|
||||
public typealias Magnitude = Foundation.TimeInterval
|
||||
public var magnitude: Foundation.TimeInterval
|
||||
public var timeInterval: Foundation.TimeInterval {
|
||||
get
|
||||
}
|
||||
public init(integerLiteral value: Foundation.TimeInterval)
|
||||
public init(floatLiteral value: Foundation.TimeInterval)
|
||||
public init(_ timeInterval: Foundation.TimeInterval)
|
||||
public init?<T>(exactly source: T) where T : Swift.BinaryInteger
|
||||
public static func < (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
public static func * (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func + (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func - (lhs: Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func *= (lhs: inout Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride)
|
||||
public static func += (lhs: inout Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride)
|
||||
public static func -= (lhs: inout Foundation.OperationQueue.SchedulerTimeType.Stride, rhs: Foundation.OperationQueue.SchedulerTimeType.Stride)
|
||||
public static func seconds(_ s: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func seconds(_ s: Swift.Double) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func milliseconds(_ ms: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func microseconds(_ us: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public static func nanoseconds(_ ns: Swift.Int) -> Foundation.OperationQueue.SchedulerTimeType.Stride
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public static func == (a: Foundation.OperationQueue.SchedulerTimeType.Stride, b: Foundation.OperationQueue.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
}
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public var hashValue: Swift.Int {
|
||||
get
|
||||
}
|
||||
public func hash(into hasher: inout Swift.Hasher)
|
||||
}
|
||||
public struct SchedulerOptions {
|
||||
}
|
||||
public func schedule(options: Foundation.OperationQueue.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.OperationQueue.SchedulerTimeType, tolerance: Foundation.OperationQueue.SchedulerTimeType.Stride, options: Foundation.OperationQueue.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.OperationQueue.SchedulerTimeType, interval: Foundation.OperationQueue.SchedulerTimeType.Stride, tolerance: Foundation.OperationQueue.SchedulerTimeType.Stride, options: Foundation.OperationQueue.SchedulerOptions?, _ action: @escaping () -> Swift.Void) -> Combine.Cancellable
|
||||
public var now: Foundation.OperationQueue.SchedulerTimeType {
|
||||
get
|
||||
}
|
||||
public var minimumTolerance: Foundation.OperationQueue.SchedulerTimeType.Stride {
|
||||
get
|
||||
}
|
||||
}
|
||||
|
||||
extension RunLoop : Combine.Scheduler {
|
||||
public struct SchedulerTimeType : Swift.Strideable, Swift.Codable, Swift.Hashable {
|
||||
public var date: Foundation.Date
|
||||
public init(_ date: Foundation.Date)
|
||||
public func distance(to other: Foundation.RunLoop.SchedulerTimeType) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public func advanced(by n: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType
|
||||
public struct Stride : Swift.ExpressibleByFloatLiteral, Swift.Comparable, Swift.SignedNumeric, Swift.Codable, Combine.SchedulerTimeIntervalConvertible {
|
||||
public typealias FloatLiteralType = Foundation.TimeInterval
|
||||
public typealias IntegerLiteralType = Foundation.TimeInterval
|
||||
public typealias Magnitude = Foundation.TimeInterval
|
||||
public var magnitude: Foundation.TimeInterval
|
||||
public var timeInterval: Foundation.TimeInterval {
|
||||
get
|
||||
}
|
||||
public init(integerLiteral value: Foundation.TimeInterval)
|
||||
public init(floatLiteral value: Foundation.TimeInterval)
|
||||
public init(_ timeInterval: Foundation.TimeInterval)
|
||||
public init?<T>(exactly source: T) where T : Swift.BinaryInteger
|
||||
public static func < (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
public static func * (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func + (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func - (lhs: Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func *= (lhs: inout Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride)
|
||||
public static func += (lhs: inout Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride)
|
||||
public static func -= (lhs: inout Foundation.RunLoop.SchedulerTimeType.Stride, rhs: Foundation.RunLoop.SchedulerTimeType.Stride)
|
||||
public static func seconds(_ s: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func seconds(_ s: Swift.Double) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func milliseconds(_ ms: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func microseconds(_ us: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public static func nanoseconds(_ ns: Swift.Int) -> Foundation.RunLoop.SchedulerTimeType.Stride
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public static func == (a: Foundation.RunLoop.SchedulerTimeType.Stride, b: Foundation.RunLoop.SchedulerTimeType.Stride) -> Swift.Bool
|
||||
}
|
||||
public init(from decoder: Swift.Decoder) throws
|
||||
public func encode(to encoder: Swift.Encoder) throws
|
||||
public var hashValue: Swift.Int {
|
||||
get
|
||||
}
|
||||
public func hash(into hasher: inout Swift.Hasher)
|
||||
}
|
||||
public struct SchedulerOptions {
|
||||
}
|
||||
public func schedule(options: Foundation.RunLoop.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.RunLoop.SchedulerTimeType, tolerance: Foundation.RunLoop.SchedulerTimeType.Stride, options: Foundation.RunLoop.SchedulerOptions?, _ action: @escaping () -> Swift.Void)
|
||||
public func schedule(after date: Foundation.RunLoop.SchedulerTimeType, interval: Foundation.RunLoop.SchedulerTimeType.Stride, tolerance: Foundation.RunLoop.SchedulerTimeType.Stride, options: Foundation.RunLoop.SchedulerOptions?, _ action: @escaping () -> Swift.Void) -> Combine.Cancellable
|
||||
public var now: Foundation.RunLoop.SchedulerTimeType {
|
||||
get
|
||||
}
|
||||
public var minimumTolerance: Foundation.RunLoop.SchedulerTimeType.Stride {
|
||||
get
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
//
|
||||
// COpenCombineHelpers.cpp
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23/09/2019.
|
||||
//
|
||||
|
||||
#include "COpenCombineHelpers.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <system_error>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <os/lock.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
// Throwing exceptions through language boundaries is undefined behavior,
|
||||
// so we must catch all of them in our extern "C" functions.
|
||||
#define OPENCOMBINE_HANDLE_EXCEPTION_BEGIN try {
|
||||
|
||||
// std::terminate will print the type and the error message of the in-flight exception.
|
||||
#define OPENCOMBINE_HANDLE_EXCEPTION_END } catch (...) { std::terminate(); }
|
||||
|
||||
// See 'double expansion trick'
|
||||
#define OPENCOMBINE_STRINGIFY(value) #value
|
||||
#define OPENCOMBINE_STRINGIFY_(value) OPENCOMBINE_STRINGIFY(value)
|
||||
#define OPENCOMBINE_STRING_LINE_NUMBER OPENCOMBINE_STRINGIFY_(__LINE__)
|
||||
|
||||
// Throw an exception if the argument is non-zero with filename and line where the error
|
||||
// occured.
|
||||
#define OPENCOMBINE_HANDLE_PTHREAD_CALL(errc) \
|
||||
if ((errc) != 0) { \
|
||||
const char* what = __FILE__ ":" OPENCOMBINE_STRING_LINE_NUMBER ": " #errc; \
|
||||
throw std::system_error((errc), std::system_category(), what); \
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<uint64_t> next_combine_identifier;
|
||||
|
||||
class PlatformIndependentMutex {
|
||||
public:
|
||||
virtual void lock() = 0;
|
||||
virtual void unlock() = 0;
|
||||
virtual void assertOwner() {}
|
||||
|
||||
virtual ~PlatformIndependentMutex() noexcept(false) {}
|
||||
};
|
||||
|
||||
class PThreadMutex : PlatformIndependentMutex {
|
||||
private:
|
||||
pthread_mutex_t mutex_;
|
||||
public:
|
||||
PThreadMutex() {
|
||||
Attributes attrs;
|
||||
attrs.setErrorCheck();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
PThreadMutex(const PThreadMutex&) = delete;
|
||||
PThreadMutex& operator=(const PThreadMutex&) = delete;
|
||||
|
||||
PThreadMutex(PThreadMutex&&) = delete;
|
||||
PThreadMutex& operator=(PThreadMutex&&) = delete;
|
||||
|
||||
void lock() override final {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_lock(&mutex_));
|
||||
}
|
||||
|
||||
void unlock() override final {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_unlock(&mutex_));
|
||||
}
|
||||
|
||||
~PThreadMutex() {
|
||||
// Yep, this destructor may throw. This is deliberate, since pthread_mutex_destroy
|
||||
// may fail.
|
||||
//
|
||||
// The altrenative is to just silently ignore the error, which is even worse.
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_destroy(&mutex_));
|
||||
}
|
||||
protected:
|
||||
class Attributes {
|
||||
pthread_mutexattr_t attrs_;
|
||||
public:
|
||||
Attributes() {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_init(&attrs_));
|
||||
}
|
||||
|
||||
Attributes(const Attributes&) = delete;
|
||||
Attributes& operator=(const Attributes&) = delete;
|
||||
|
||||
Attributes(Attributes&&) = delete;
|
||||
Attributes& operator=(Attributes&&) = delete;
|
||||
|
||||
const pthread_mutexattr_t* raw() const noexcept {
|
||||
return &attrs_;
|
||||
}
|
||||
|
||||
void setRecursive() {
|
||||
setType(PTHREAD_MUTEX_RECURSIVE);
|
||||
}
|
||||
|
||||
void setErrorCheck() {
|
||||
setType(PTHREAD_MUTEX_ERRORCHECK);
|
||||
}
|
||||
|
||||
~Attributes() noexcept(false) {
|
||||
// Yep, this destructor may throw. This is deliberate,
|
||||
// since pthread_mutexattr_destroy may fail.
|
||||
//
|
||||
// The altrenative is to just silently ignore the error, which is even worse.
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_destroy(&attrs_));
|
||||
}
|
||||
private:
|
||||
void setType(int type) {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_settype(&attrs_, type));
|
||||
}
|
||||
};
|
||||
|
||||
void initialize(const Attributes& attributes) {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_init(&mutex_, attributes.raw()));
|
||||
}
|
||||
};
|
||||
|
||||
class PThreadRecursiveMutex final : PThreadMutex {
|
||||
public:
|
||||
PThreadRecursiveMutex() {
|
||||
Attributes attrs;
|
||||
attrs.setRecursive();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
PThreadRecursiveMutex(const PThreadRecursiveMutex&) = delete;
|
||||
PThreadRecursiveMutex& operator=(const PThreadRecursiveMutex&) = delete;
|
||||
|
||||
PThreadRecursiveMutex(PThreadRecursiveMutex&&) = delete;
|
||||
PThreadRecursiveMutex& operator=(PThreadRecursiveMutex&&) = delete;
|
||||
};
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
class OS_UNFAIR_LOCK_AVAILABILITY OSUnfairLock final : PlatformIndependentMutex {
|
||||
os_unfair_lock mutex_ = OS_UNFAIR_LOCK_INIT;
|
||||
public:
|
||||
OSUnfairLock() = default;
|
||||
|
||||
OSUnfairLock(const OSUnfairLock&) = delete;
|
||||
OSUnfairLock& operator=(const OSUnfairLock&) = delete;
|
||||
|
||||
OSUnfairLock(OSUnfairLock&&) = delete;
|
||||
OSUnfairLock& operator=(OSUnfairLock&&) = delete;
|
||||
|
||||
void lock() override {
|
||||
os_unfair_lock_lock(&mutex_);
|
||||
}
|
||||
|
||||
void unlock() override {
|
||||
os_unfair_lock_unlock(&mutex_);
|
||||
}
|
||||
|
||||
void assertOwner() override {
|
||||
os_unfair_lock_assert_owner(&mutex_);
|
||||
}
|
||||
};
|
||||
#endif // __APPLE__
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
uint64_t opencombine_next_combine_identifier(void) {
|
||||
return next_combine_identifier.fetch_add(1);
|
||||
}
|
||||
|
||||
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (__builtin_available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) {
|
||||
return {new OSUnfairLock};
|
||||
} else {
|
||||
return {new PThreadMutex};
|
||||
}
|
||||
#else
|
||||
return {new PThreadMutex};
|
||||
#endif
|
||||
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
// TODO: Use os_unfair_recursive_lock on Darwin as soon as it becomes public API.
|
||||
return {new PThreadRecursiveMutex};
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_lock(OpenCombineUnfairLock lock) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock mutex) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(mutex.opaque)->unlock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(mutex.opaque)->assertOwner();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock lock) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock mutex) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(mutex.opaque)->unlock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock) {
|
||||
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
|
||||
}
|
||||
|
||||
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock) {
|
||||
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// COpenCombineHelpers.h
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23/09/2019.
|
||||
//
|
||||
|
||||
#ifndef COPENCOMBINEHELPERS_H
|
||||
#define COPENCOMBINEHELPERS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
|
||||
#if __has_attribute(swift_name)
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
|
||||
#else
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name)
|
||||
#endif
|
||||
|
||||
#if __has_attribute(always_inline)
|
||||
# define OPENCOMBINE_ALWAYS_INLINE __attribute__((always_inline))
|
||||
#else
|
||||
# define OPENCOMBINE_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma mark - CombineIdentifier
|
||||
|
||||
uint64_t opencombine_next_combine_identifier(void)
|
||||
OPENCOMBINE_SWIFT_NAME(__nextCombineIdentifier());
|
||||
|
||||
#pragma mark - OpenCombineUnfairLock
|
||||
|
||||
/// A wrapper around an opaque pointer for type safety in Swift.
|
||||
typedef struct OpenCombineUnfairLock {
|
||||
void* _Nonnull opaque;
|
||||
} OPENCOMBINE_SWIFT_NAME(__UnfairLock) OpenCombineUnfairLock;
|
||||
|
||||
/// Allocates a lock object. The allocated object must be destroyed by calling
|
||||
/// the destroy() method.
|
||||
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairLock.allocate());
|
||||
|
||||
void opencombine_unfair_lock_lock(OpenCombineUnfairLock)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairLock.lock(self:));
|
||||
|
||||
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairLock.unlock(self:));
|
||||
|
||||
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairLock.assertOwner(self:));
|
||||
|
||||
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairLock.deallocate(self:));
|
||||
|
||||
#pragma mark - OpenCombineUnfairRecursiveLock
|
||||
|
||||
/// A wrapper around an opaque pointer for type safety in Swift.
|
||||
typedef struct OpenCombineUnfairRecursiveLock {
|
||||
void* _Nonnull opaque;
|
||||
} OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock) OpenCombineUnfairRecursiveLock;
|
||||
|
||||
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.allocate());
|
||||
|
||||
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.lock(self:));
|
||||
|
||||
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.unlock(self:));
|
||||
|
||||
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock)
|
||||
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.deallocate(self:));
|
||||
|
||||
#pragma mark - Breakpoint
|
||||
|
||||
OPENCOMBINE_ALWAYS_INLINE
|
||||
inline void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
|
||||
|
||||
void opencombine_stop_in_debugger(void) {
|
||||
raise(SIGTRAP);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* COPENCOMBINEHELPERS_H */
|
||||
@@ -10,6 +10,7 @@
|
||||
/// 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.
|
||||
public final class AnyCancellable: Cancellable, Hashable {
|
||||
|
||||
private var _cancel: (() -> Void)?
|
||||
|
||||
@@ -5,6 +5,17 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Wraps this publisher with a type eraser.
|
||||
///
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher` to
|
||||
/// the downstream subscriber, rather than this publisher’s actual type.
|
||||
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing publisher.
|
||||
///
|
||||
/// Use `AnyPublisher` to wrap a publisher whose type has details you don’t want to expose
|
||||
@@ -13,7 +24,6 @@ public struct AnyPublisher<Output, Failure: Error>
|
||||
: CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
|
||||
@usableFromInline
|
||||
internal let box: PublisherBoxBase<Output, Failure>
|
||||
|
||||
@@ -47,8 +57,8 @@ extension AnyPublisher: Publisher {
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
@inlinable
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
box.subscribe(subscriber)
|
||||
}
|
||||
@@ -62,11 +72,11 @@ internal class PublisherBoxBase<Output, Failure: Error>: Publisher {
|
||||
@inlinable
|
||||
internal init() {}
|
||||
|
||||
@inlinable
|
||||
internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
@usableFromInline
|
||||
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +94,8 @@ internal final class PublisherBox<PublisherType: Publisher>
|
||||
}
|
||||
|
||||
@inlinable
|
||||
override internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
override internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
base.subscribe(subscriber)
|
||||
}
|
||||
|
||||
@@ -137,17 +137,17 @@ internal class AnySubscriberBase<Input, Failure: Error>: Subscriber {
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(subscription: Subscription) {
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,52 +222,3 @@ internal final class ClosureBasedAnySubscriber<Input, Failure: Error>
|
||||
receiveCompletionThunk(completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal final class SubjectSubscriber<Downstream: Subject>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
Subscription
|
||||
{
|
||||
internal var downstreamSubject: Downstream?
|
||||
internal var upstreamSubscription: Subscription?
|
||||
|
||||
internal init(_ parent: Downstream) {
|
||||
self.downstreamSubject = parent
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
guard upstreamSubscription == nil else { return }
|
||||
upstreamSubscription = subscription
|
||||
downstreamSubject?.send(subscription: self)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
|
||||
downstreamSubject?.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
downstreamSubject?.send(completion: completion)
|
||||
}
|
||||
|
||||
internal var description: String { return "Subject" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
let children: [(label: String?, value: Any)] = [
|
||||
(label: "downstreamSubject", value: downstreamSubject as Any),
|
||||
(label: "upstreamSubscription", value: upstreamSubscription as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
downstreamSubject = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,35 +5,23 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
public struct CombineIdentifier: Hashable, CustomStringConvertible {
|
||||
|
||||
@usableFromInline
|
||||
internal static var _counter: UInt = 0
|
||||
private let value: UInt64
|
||||
|
||||
@usableFromInline
|
||||
internal static var _counterLock = Lock(recursive: false)
|
||||
|
||||
@usableFromInline
|
||||
internal let _id: UInt
|
||||
|
||||
@inlinable
|
||||
public init() {
|
||||
|
||||
var id: UInt = 0
|
||||
|
||||
CombineIdentifier._counterLock.do {
|
||||
id = CombineIdentifier._counter
|
||||
CombineIdentifier._counter += 1
|
||||
}
|
||||
|
||||
_id = id
|
||||
value = __nextCombineIdentifier()
|
||||
}
|
||||
|
||||
public init(_ obj: AnyObject) {
|
||||
_id = UInt(bitPattern: ObjectIdentifier(obj))
|
||||
value = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "0x\(String(_id, radix: 16))"
|
||||
return "0x\(String(value, radix: 16))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
/// changes.
|
||||
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _lock = Lock(recursive: true)
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
private var _value: Output
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
@@ -22,8 +24,11 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
/// The value wrapped by this subject, published as a new element whenever it changes.
|
||||
public var value: Output {
|
||||
didSet {
|
||||
send(value)
|
||||
get {
|
||||
return _value
|
||||
}
|
||||
set {
|
||||
send(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +36,14 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
///
|
||||
/// - Parameter value: The initial value to publish.
|
||||
public init(_ value: Output) {
|
||||
self.value = value
|
||||
self._value = value
|
||||
}
|
||||
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
@@ -68,6 +74,7 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
public func send(_ input: Output) {
|
||||
_lock.do {
|
||||
_value = input
|
||||
for subscription in _subscriptions where !subscription.isCompleted {
|
||||
if subscription._demand > 0 {
|
||||
subscription._offer(input)
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Future.swift
|
||||
//
|
||||
//
|
||||
// Created by Max Desiatov on 24/11/2019.
|
||||
//
|
||||
|
||||
/// A publisher that eventually produces one value and then finishes or fails.
|
||||
public final class Future<Output, Failure>: Publisher where Failure: Error {
|
||||
|
||||
public typealias Promise = (Result<Output, Failure>) -> Void
|
||||
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
private var result: Result<Output, Failure>?
|
||||
|
||||
public init(
|
||||
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
|
||||
) {
|
||||
attemptToFulfill { result in
|
||||
self._lock.do {
|
||||
guard self.result == nil else { return }
|
||||
self.result = result
|
||||
self._publish(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this
|
||||
/// `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(
|
||||
subscriber: Downstream
|
||||
) where Output == Downstream.Input, Failure == Downstream.Failure {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
private func _acknowledgeDownstreamDemand() {
|
||||
_lock.do {
|
||||
guard let result = result else { return }
|
||||
_publish(result)
|
||||
}
|
||||
}
|
||||
|
||||
private func _publish(_ result: Result<Output, Failure>) {
|
||||
for subscription in self._subscriptions where !subscription._isCompleted {
|
||||
switch result {
|
||||
case let .success(output) where subscription._demand > 0:
|
||||
subscription._demand -= 1
|
||||
subscription._demand += subscription._downstream?.receive(output) ?? .none
|
||||
subscription._receive(completion: .finished)
|
||||
case let .failure(error):
|
||||
subscription._receive(completion: .failure(error))
|
||||
|
||||
// nothing to do if no demand
|
||||
default: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Future {
|
||||
|
||||
fileprivate final class Conduit: Subscription {
|
||||
|
||||
fileprivate var _parent: Future<Output, Failure>?
|
||||
|
||||
fileprivate var _downstream: AnySubscriber<Output, Failure>?
|
||||
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
|
||||
fileprivate var _isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
|
||||
fileprivate init(parent: Future<Output, Failure>,
|
||||
downstream: AnySubscriber<Output, Failure>) {
|
||||
_parent = parent
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !_isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
_parent?._lock.do {
|
||||
_demand += demand
|
||||
}
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
fileprivate func cancel() {
|
||||
_parent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Future.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "Future" }
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
//
|
||||
// FilterProducer.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23.10.2019.
|
||||
//
|
||||
|
||||
/// A helper class that acts like both subscriber and subscription.
|
||||
///
|
||||
/// Filter-like operators send an instance of their `Inner` class that is subclass
|
||||
/// of this class to the upstream publisher (as subscriber) and
|
||||
/// to the downstream subcriber (as subscription).
|
||||
///
|
||||
/// Filter-like operators include `Publishers.Filter`,
|
||||
/// `Publishers.RemoveDuplicates`, `Publishers.PrefixWhile` and more.
|
||||
///
|
||||
/// Subclasses must override the `receive(newValue:)` and `description`.
|
||||
internal class FilterProducer<Downstream: Subscriber,
|
||||
Input,
|
||||
Output,
|
||||
UpstreamFailure: Error,
|
||||
Filter>
|
||||
: CustomStringConvertible,
|
||||
CustomReflectable
|
||||
where Downstream.Input == Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
// MARK: - State
|
||||
|
||||
private enum State {
|
||||
case awaitingSubscription
|
||||
case connected(Subscription)
|
||||
case completed
|
||||
}
|
||||
|
||||
internal final let filter: Filter
|
||||
|
||||
internal final let downstream: Downstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state = State.awaitingSubscription
|
||||
|
||||
internal init(downstream: Downstream, filter: Filter) {
|
||||
self.downstream = downstream
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
// MARK: - Abstract methods
|
||||
|
||||
internal func receive(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Output?, Downstream.Failure> {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
// MARK: - CustomReflectable
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
}
|
||||
|
||||
extension FilterProducer: Subscriber {
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .connected(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription:
|
||||
lock.unlock()
|
||||
fatalError("Invalid state: Received value before receiving subscription")
|
||||
case .completed:
|
||||
lock.unlock()
|
||||
case let .connected(subscription):
|
||||
lock.unlock()
|
||||
switch receive(newValue: input) {
|
||||
case let .continue(output?):
|
||||
return downstream.receive(output)
|
||||
case .continue(nil):
|
||||
return .max(1)
|
||||
case .finished:
|
||||
lock.lock()
|
||||
state = .completed
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .finished)
|
||||
case let .failure(error):
|
||||
lock.lock()
|
||||
state = .completed
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<UpstreamFailure>) {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription:
|
||||
lock.unlock()
|
||||
fatalError("Invalid state: Received completion before receiving subscription")
|
||||
case .completed:
|
||||
lock.unlock()
|
||||
return
|
||||
case .connected:
|
||||
state = .completed
|
||||
lock.unlock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case let .failure(failure):
|
||||
downstream.receive(completion: .failure(failure as! Downstream.Failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FilterProducer: Subscription {
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription:
|
||||
lock.unlock()
|
||||
fatalError("Invalid state: Received request before sending subscription")
|
||||
case .completed:
|
||||
lock.unlock()
|
||||
return
|
||||
case let .connected(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
guard case let .connected(subscription) = state else {
|
||||
state = .completed
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .completed
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension FilterProducer: CustomPlaygroundDisplayConvertible {
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Locking.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
internal typealias UnfairLock = __UnfairLock
|
||||
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
|
||||
|
||||
extension UnfairRecursiveLock {
|
||||
|
||||
@inlinable
|
||||
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
|
||||
lock()
|
||||
defer { unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// PartialCompletion.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 22.09.2019.
|
||||
//
|
||||
|
||||
/// A value of this type is returned by the overridden `receive(newValue:)` method
|
||||
/// of the `ReduceProducer` and `FilterProducer` classes.
|
||||
internal enum PartialCompletion<Value, Failure: Error> {
|
||||
|
||||
/// Indicate that we should continue accepting the upstream's output.
|
||||
case `continue`(Value)
|
||||
|
||||
/// Indicate that no values should be received from the upstream anymore.
|
||||
case finished
|
||||
|
||||
/// Indicate that there was a failure and we should send it downstream.
|
||||
case failure(Failure)
|
||||
}
|
||||
|
||||
extension PartialCompletion where Value == Void {
|
||||
|
||||
/// Indicate that we should continue accepting the upstream's output.
|
||||
internal static var `continue`: PartialCompletion { return .continue(()) }
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
//
|
||||
// ReduceProducer.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 22.09.2019.
|
||||
//
|
||||
|
||||
/// A helper class that acts like both subscriber and subscription.
|
||||
///
|
||||
/// Reduce-like operators send an instance of their `Inner` class that is subclass
|
||||
/// of this class to the upstream publisher (as subscriber) and
|
||||
/// to the downstream subcriber (as subsription).
|
||||
///
|
||||
/// Reduce-like operators include `Publishers.Reduce`, `Publishers.TryReduce`,
|
||||
/// `Publishers.Count`, `Publishers.FirstWhere`, `Publishers.AllSatisfy` and more.
|
||||
///
|
||||
/// Subclasses must override the `receive(newValue:)` and `description`.
|
||||
internal class ReduceProducer<Downstream: Subscriber,
|
||||
Input,
|
||||
Output,
|
||||
UpstreamFailure: Error,
|
||||
Reducer>
|
||||
: CustomStringConvertible,
|
||||
CustomReflectable
|
||||
where Downstream.Input == Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
// MARK: - State
|
||||
|
||||
internal final var result: Output?
|
||||
|
||||
private let initial: Output?
|
||||
|
||||
internal final let reduce: Reducer
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamRequested = false
|
||||
|
||||
private var cancelled = false
|
||||
|
||||
private var completed = false
|
||||
|
||||
private var upstreamCompleted = false
|
||||
|
||||
private var empty = true
|
||||
|
||||
internal init(downstream: Downstream, initial: Output?, reduce: Reducer) {
|
||||
self.downstream = downstream
|
||||
self.initial = initial
|
||||
self.result = initial
|
||||
self.reduce = reduce
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
// MARK: - Abstract methods
|
||||
|
||||
internal func receive(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
// MARK: - CustomReflectable
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("result", result as Any),
|
||||
("initial", initial as Any),
|
||||
("status", status)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// - Precondition: `lock` is held.
|
||||
private func receiveFinished() {
|
||||
guard !cancelled, !completed, !upstreamCompleted else {
|
||||
lock.unlock()
|
||||
// This should never happen, because `receive(completion:)`
|
||||
// (from which this function is called) early exists if
|
||||
// `status` is `.terminal`.
|
||||
assertionFailure("The subscription should have been terminated by now")
|
||||
return
|
||||
}
|
||||
upstreamCompleted = true
|
||||
self.completed = downstreamRequested || empty
|
||||
let completed = self.completed
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
|
||||
if completed {
|
||||
sendResultAndFinish(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Precondition: `lock` is held.
|
||||
private func receiveFailure(_ failure: UpstreamFailure) {
|
||||
guard !cancelled, !completed, !upstreamCompleted else {
|
||||
lock.unlock()
|
||||
// This should never happen, because `receive(completion:)`
|
||||
// (from which this function is called) early exists if
|
||||
// `status` is `.terminal`.
|
||||
assertionFailure("The subscription should have been terminated by now")
|
||||
return
|
||||
}
|
||||
upstreamCompleted = true
|
||||
completed = true
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .failure(failure as! Downstream.Failure))
|
||||
}
|
||||
|
||||
private func sendResultAndFinish(_ result: Output?) {
|
||||
assert(completed && upstreamCompleted)
|
||||
if let result = result {
|
||||
_ = downstream.receive(result)
|
||||
}
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
}
|
||||
|
||||
extension ReduceProducer: Subscriber {
|
||||
internal func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
empty = false
|
||||
lock.unlock()
|
||||
|
||||
// Combine doesn't hold the lock when calling `receive(newValue:)`.
|
||||
//
|
||||
// This can lead to data races if the contract is violated
|
||||
// (like when we receive input from multiple threads simultaneously).
|
||||
switch self.receive(newValue: input) {
|
||||
case .continue:
|
||||
break
|
||||
case .finished:
|
||||
lock.lock()
|
||||
upstreamCompleted = true
|
||||
let downstreamRequested = self.downstreamRequested
|
||||
if downstreamRequested {
|
||||
completed = true
|
||||
}
|
||||
status = .terminal
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
|
||||
subscription.cancel()
|
||||
|
||||
guard downstreamRequested else { break }
|
||||
|
||||
sendResultAndFinish(result)
|
||||
case let .failure(error):
|
||||
lock.lock()
|
||||
upstreamCompleted = true
|
||||
completed = true
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<UpstreamFailure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
switch completion {
|
||||
case .finished:
|
||||
receiveFinished()
|
||||
case let .failure(error):
|
||||
receiveFailure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ReduceProducer: Subscription {
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard !downstreamRequested, !cancelled, !completed else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamRequested = true
|
||||
guard upstreamCompleted else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
completed = true
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
sendResultAndFinish(result)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension ReduceProducer: CustomPlaygroundDisplayConvertible {
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// SubjectSubscriber.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16/09/2019.
|
||||
//
|
||||
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
internal final class SubjectSubscriber<Downstream: Subject>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible,
|
||||
Subscription
|
||||
{
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var downstreamSubject: Downstream?
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
private var isCancelled: Bool { return downstreamSubject == nil }
|
||||
|
||||
internal init(_ parent: Downstream) {
|
||||
self.downstreamSubject = parent
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard upstreamSubscription == nil, let subject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = subscription
|
||||
lock.unlock()
|
||||
subject.send(subscription: self)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard let downstreamSubject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
guard upstreamSubscription != nil else { APIViolationValueBeforeSubscription() }
|
||||
lock.unlock()
|
||||
downstreamSubject.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
lock.lock()
|
||||
guard let subject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
guard upstreamSubscription != nil else { APIViolationUnexpectedCompletion() }
|
||||
lock.unlock()
|
||||
subject.send(completion: completion)
|
||||
downstreamSubject = nil
|
||||
}
|
||||
|
||||
internal var description: String { return "Subject" }
|
||||
|
||||
internal var playgroundDescription: Any { return description }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstreamSubject", downstreamSubject as Any),
|
||||
("upstreamSubscription", upstreamSubscription as Any),
|
||||
("subject", downstreamSubject as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard let subscription = upstreamSubscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
if isCancelled {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
guard let subscription = upstreamSubscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = nil
|
||||
downstreamSubject = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// SubscriptionStatus.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 21.09.2019.
|
||||
//
|
||||
|
||||
internal enum SubscriptionStatus {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case terminal
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Violations.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16/09/2019.
|
||||
//
|
||||
|
||||
internal func APIViolationValueBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("""
|
||||
API Violation: received an unexpected value before receiving a Subscription
|
||||
""",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
internal func APIViolationUnexpectedCompletion(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("API Violation: received an unexpected completion", file: file, line: line)
|
||||
}
|
||||
|
||||
internal func abstractMethod(file: StaticString = #file, line: UInt = #line) -> Never {
|
||||
fatalError("Abstract method call", file: file, line: line)
|
||||
}
|
||||
|
||||
extension Subscribers.Demand {
|
||||
internal func assertNonZero(file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
if self == .none {
|
||||
fatalError("API Violation: demand must not be zero", file: file, line: line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
/// A scheduler for performing synchronous actions.
|
||||
///
|
||||
/// You can use this scheduler for immediate actions. If you attempt to schedule
|
||||
/// actions after a specific date, the scheduler produces a fatal error.
|
||||
/// 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.
|
||||
public struct ImmediateScheduler: Scheduler {
|
||||
|
||||
/// The time type used by the immediate scheduler.
|
||||
@@ -139,9 +140,7 @@ public struct ImmediateScheduler: Scheduler {
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
fatalError(
|
||||
"Attempt to schedule something in the future on the immediate scheduler"
|
||||
)
|
||||
action()
|
||||
}
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
@@ -151,8 +150,7 @@ public struct ImmediateScheduler: Scheduler {
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
fatalError(
|
||||
"Attempt to schedule something in the future on the immediate scheduler"
|
||||
)
|
||||
action()
|
||||
return Subscriptions.empty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// Locking.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(Darwin)
|
||||
import Darwin
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#else
|
||||
#error("How to do locking on this platform?")
|
||||
#endif
|
||||
|
||||
@usableFromInline
|
||||
internal final class Lock {
|
||||
|
||||
@usableFromInline
|
||||
internal var _mutex = pthread_mutex_t()
|
||||
|
||||
@inlinable
|
||||
internal init(recursive: Bool) {
|
||||
var attrib = pthread_mutexattr_t()
|
||||
pthread_mutexattr_init(&attrib)
|
||||
if recursive {
|
||||
pthread_mutexattr_settype(&attrib, Int32(PTHREAD_MUTEX_RECURSIVE))
|
||||
}
|
||||
pthread_mutex_init(&_mutex, &attrib)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
deinit {
|
||||
pthread_mutex_destroy(&_mutex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func _lock() {
|
||||
pthread_mutex_lock(&_mutex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func _unlock() {
|
||||
pthread_mutex_unlock(&_mutex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
|
||||
_lock()
|
||||
defer { _unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// ObservableObject.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08/09/2019.
|
||||
//
|
||||
|
||||
/// 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:
|
||||
///
|
||||
/// class Contact : ObservableObject {
|
||||
/// @Published var name: String
|
||||
/// @Published var age: Int
|
||||
///
|
||||
/// init(name: String, age: Int) {
|
||||
/// self.name = name
|
||||
/// self.age = age
|
||||
/// }
|
||||
///
|
||||
/// func haveBirthday() -> Int {
|
||||
/// age += 1
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let john = Contact(name: "John Appleseed", age: 24)
|
||||
/// john.objectWillChange.sink { _ in print("will change") }
|
||||
/// print(john.haveBirthday)
|
||||
/// // Prints "will change"
|
||||
/// // Prints "25"
|
||||
///
|
||||
public protocol ObservableObject: AnyObject {
|
||||
|
||||
/// The type of publisher that emits before the object has changed.
|
||||
associatedtype ObjectWillChangePublisher: Publisher = ObservableObjectPublisher
|
||||
where ObjectWillChangePublisher.Failure == Never
|
||||
|
||||
/// A publisher that emits before the object has changed.
|
||||
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
|
||||
}
|
||||
|
||||
/// The default publisher of an `ObservableObject`.
|
||||
public final class ObservableObjectPublisher: Publisher {
|
||||
|
||||
public typealias Output = Void
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var connections = Set<Conduit>()
|
||||
|
||||
// TODO: Combine needs this for some reason
|
||||
private var identifier: ObjectIdentifier?
|
||||
|
||||
public init() {}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Void, Downstream.Failure == Never
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, parent: self)
|
||||
lock.lock()
|
||||
connections.insert(inner)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
|
||||
public func send() {
|
||||
lock.lock()
|
||||
let connections = self.connections
|
||||
lock.unlock()
|
||||
for connection in connections {
|
||||
connection.send()
|
||||
}
|
||||
}
|
||||
|
||||
private func remove(_ conduit: Conduit) {
|
||||
lock.lock()
|
||||
connections.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension ObservableObjectPublisher {
|
||||
private class Conduit: Hashable {
|
||||
|
||||
fileprivate func send() {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
fileprivate static func == (lhs: Conduit, rhs: Conduit) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
fileprivate func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Conduit,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Void, Downstream.Failure == Never
|
||||
{
|
||||
private enum State {
|
||||
case initialized
|
||||
case active
|
||||
case terminal
|
||||
}
|
||||
|
||||
private weak var parent: ObservableObjectPublisher?
|
||||
private let downstream: Downstream
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var state = State.initialized
|
||||
|
||||
init(downstream: Downstream, parent: ObservableObjectPublisher) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
downstreamLock.deallocate()
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
override func send() {
|
||||
lock.lock()
|
||||
let state = self.state
|
||||
lock.unlock()
|
||||
if state == .active {
|
||||
downstreamLock.lock()
|
||||
_ = downstream.receive()
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
if state == .initialized {
|
||||
state = .active
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
parent?.remove(self)
|
||||
}
|
||||
|
||||
var description: String { return "ObservableObjectPublisher" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any {
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
/// specific values on-demand during tests.
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _lock = Lock(recursive: true)
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
@@ -28,6 +28,7 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
@@ -39,8 +40,8 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
_lock.do {
|
||||
if let completion = _completion {
|
||||
@@ -117,9 +118,9 @@ extension PassthroughSubject {
|
||||
}
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
precondition(demand > 0, "demand must not be zero")
|
||||
demand.assertNonZero()
|
||||
_parent?._lock.do {
|
||||
_demand = demand
|
||||
_demand += demand
|
||||
}
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Published.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Евгений Богомолов on 01/09/2019.
|
||||
//
|
||||
|
||||
#if swift(>=5.1)
|
||||
/// Adds a `Publisher` to a property.
|
||||
///
|
||||
/// 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.
|
||||
@available(swift, introduced: 5.1)
|
||||
@propertyWrapper
|
||||
public struct Published<Value> {
|
||||
|
||||
/// Initialize the storage of the `Published` property as well as the corresponding
|
||||
/// `Publisher`.
|
||||
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 {
|
||||
|
||||
public typealias Output = Value
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable let_var_whitespace
|
||||
@available(*, unavailable, message: """
|
||||
@Published is only available on properties of classes
|
||||
""")
|
||||
public var wrappedValue: Value {
|
||||
get { fatalError() }
|
||||
set { fatalError() } // swiftlint:disable:this unused_setter_value
|
||||
}
|
||||
// swiftlint:enable let_var_whitespace
|
||||
|
||||
public static subscript<EnclosingSelf: AnyObject>(
|
||||
_enclosingInstance object: EnclosingSelf,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
|
||||
) -> Value {
|
||||
get {
|
||||
return object[keyPath: storageKeyPath].value
|
||||
}
|
||||
set {
|
||||
object[keyPath: storageKeyPath].objectWillChange?.send()
|
||||
object[keyPath: storageKeyPath].publisher?.subject.send(newValue)
|
||||
object[keyPath: storageKeyPath].value = newValue
|
||||
}
|
||||
// TODO: Benchmark and explore a possibility to use _modify
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
@available(swift, introduced: 5.1)
|
||||
public typealias Published = Never
|
||||
|
||||
#endif // swift(>=5.1)
|
||||
@@ -38,9 +38,9 @@ public struct Deferred<DeferredPublisher: Publisher>: Publisher {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure,
|
||||
Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure,
|
||||
Output == Downstream.Input
|
||||
{
|
||||
let deferredPublisher = createPublisher()
|
||||
deferredPublisher.subscribe(subscriber)
|
||||
|
||||
@@ -42,8 +42,8 @@ public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
/// to the subscriber. If `false`, it never completes.
|
||||
public let completeImmediately: Bool
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
if completeImmediately {
|
||||
|
||||
@@ -30,8 +30,8 @@ public struct Fail<Output, Failure: Error>: Publisher {
|
||||
/// The failure to send when terminating the publisher.
|
||||
public let error: Failure
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(error))
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.Encode.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/22/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Encodes the output from upstream using a specified `TopLevelEncoder`.
|
||||
/// For example, use `JSONEncoder`.
|
||||
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`.
|
||||
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
) -> Publishers.Decode<Self, Item, Coder> where Output == Coder.Input {
|
||||
return .init(upstream: self, decoder: decoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Encode<Upstream: Publisher, Coder: TopLevelEncoder>: Publisher
|
||||
where Upstream.Output: Encodable
|
||||
{
|
||||
public typealias Failure = Error
|
||||
|
||||
public typealias Output = Coder.Output
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
private let _encode: (Upstream.Output) throws -> Output
|
||||
|
||||
public init(upstream: Upstream, encoder: Coder) {
|
||||
self.upstream = upstream
|
||||
self._encode = encoder.encode
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, encode: _encode))
|
||||
}
|
||||
}
|
||||
|
||||
public struct Decode<Upstream: Publisher, Output: Decodable, Coder: TopLevelDecoder>
|
||||
: Publisher
|
||||
where Upstream.Output == Coder.Input
|
||||
{
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
private let _decode: (Upstream.Output) throws -> Output
|
||||
|
||||
public init(upstream: Upstream, decoder: Coder) {
|
||||
self.upstream = upstream
|
||||
self._decode = { try decoder.decode(Output.self, from: $0) }
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, decode: _decode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Encode {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
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
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let encode: (Upstream.Output) throws -> Output
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
encode: @escaping (Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.encode = encode
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
if finished || self.subscription != nil {
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if finished { return .none }
|
||||
do {
|
||||
return try downstream.receive(encode(input))
|
||||
} catch {
|
||||
finished = true
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if finished { return }
|
||||
finished = true
|
||||
subscription = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let subscription = self.subscription, !finished else { return }
|
||||
subscription.cancel()
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
}
|
||||
|
||||
var description: String { return "Encode" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("finished", finished),
|
||||
("upstreamSubscription", subscription as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Decode {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
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
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let decode: (Upstream.Output) throws -> Output
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
decode: @escaping (Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.decode = decode
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
if finished || self.subscription != nil {
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if finished { return .none }
|
||||
do {
|
||||
return try downstream.receive(decode(input))
|
||||
} catch {
|
||||
finished = true
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if finished { return }
|
||||
finished = true
|
||||
subscription = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let subscription = self.subscription, !finished else { return }
|
||||
subscription.cancel()
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
}
|
||||
|
||||
var description: String { return "Decode" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("finished", finished),
|
||||
("upstreamSubscription", subscription as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher that publishes the values of a keyt path as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - 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>
|
||||
) -> Publishers.MapKeyPath<Self, Result> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath: keyPath
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of two key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - 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>,
|
||||
_ keyPath1: KeyPath<Output, Result1>
|
||||
) -> Publishers.MapKeyPath2<Self, Result0, Result1> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath0: keyPath0,
|
||||
keyPath1: keyPath1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - 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`
|
||||
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
|
||||
public func map<Result0, Result1, Result2>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
_ keyPath1: KeyPath<Output, Result1>,
|
||||
_ keyPath2: KeyPath<Output, Result2>
|
||||
) -> Publishers.MapKeyPath3<Self, Result0, Result1, Result2> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath0: keyPath0,
|
||||
keyPath1: keyPath1,
|
||||
keyPath2: keyPath2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the value of a key path.
|
||||
public struct MapKeyPath<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath: KeyPath<Upstream.Output, Output>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of two key paths as a tuple.
|
||||
public struct MapKeyPath2<Upstream: Publisher, Output0, Output1>: Publisher {
|
||||
|
||||
public typealias Output = (Output0, Output1)
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of three key paths as a tuple.
|
||||
public struct MapKeyPath3<Upstream: Publisher, Output0, Output1, Output2>: Publisher {
|
||||
|
||||
public typealias Output = (Output0, Output1, Output2)
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
/// The key path of a third property to publish.
|
||||
public let keyPath2: KeyPath<Upstream.Output, Output2>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath: KeyPath<Input, Output>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath<Upstream, Output>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath = parent.keyPath
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKey" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("keyPath", keyPath),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath2 {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath0: KeyPath<Input, Output0>
|
||||
|
||||
private let keyPath1: KeyPath<Input, Output1>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath2<Upstream, Output0, Output1>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath0 = parent.keyPath0
|
||||
self.keyPath1 = parent.keyPath1
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath0],
|
||||
input[keyPath: keyPath1]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKeys" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("keyPath0", keyPath0),
|
||||
("keyPath1", keyPath1),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath3 {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath0: KeyPath<Input, Output0>
|
||||
|
||||
private let keyPath1: KeyPath<Input, Output1>
|
||||
|
||||
private let keyPath2: KeyPath<Input, Output2>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath3<Upstream, Output0, Output1, Output2>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath0 = parent.keyPath0
|
||||
self.keyPath1 = parent.keyPath1
|
||||
self.keyPath2 = parent.keyPath2
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath0],
|
||||
input[keyPath: keyPath1],
|
||||
input[keyPath: keyPath2]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKeys" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("keyPath0", keyPath0),
|
||||
("keyPath1", keyPath1),
|
||||
("keyPath2", keyPath2),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,952 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.Merge.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 04/10/2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
// swiftlint:disable vertical_parameter_alignment
|
||||
|
||||
// MARK: - Merge methods on Publisher
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - other: Another publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge<Self, P>
|
||||
where Failure == P.Failure, Output == P.Output
|
||||
{
|
||||
return .init(self, other)
|
||||
}
|
||||
/// Combines elements from this publisher with those from three other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher
|
||||
>(with b: B,
|
||||
_ c: C) -> Publishers.Merge3<Self, B, C>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output
|
||||
{
|
||||
return .init(self, b, c)
|
||||
}
|
||||
/// Combines elements from this publisher with those from four other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D) -> Publishers.Merge4<Self, B, C, D>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output
|
||||
{
|
||||
return .init(self, b, c, d)
|
||||
}
|
||||
/// Combines elements from this publisher with those from five other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E) -> Publishers.Merge5<Self, B, C, D, E>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output
|
||||
{
|
||||
return .init(self, b, c, d, e)
|
||||
}
|
||||
/// Combines elements from this publisher with those from six other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F) -> Publishers.Merge6<Self, B, C, D, E, F>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output
|
||||
{
|
||||
return .init(self, b, c, d, e, f)
|
||||
}
|
||||
/// Combines elements from this publisher with those from seven other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - g: A seventh publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G) -> Publishers.Merge7<Self, B, C, D, E, F, G>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output
|
||||
{
|
||||
return .init(self, b, c, d, e, f, g)
|
||||
}
|
||||
/// Combines elements from this publisher with those from eight other publishers,
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - b: A second publisher.
|
||||
/// - c: A third publisher.
|
||||
/// - d: A fourth publisher.
|
||||
/// - e: A fifth publisher.
|
||||
/// - f: A sixth publisher.
|
||||
/// - g: A seventh publisher.
|
||||
/// - h: An eighth publisher.
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
public func merge<
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher,
|
||||
H: Publisher
|
||||
>(with b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G,
|
||||
_ h: H) -> Publishers.Merge8<Self, B, C, D, E, F, G, H>
|
||||
where Failure == B.Failure, Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output,
|
||||
G.Failure == H.Failure, G.Output == H.Output
|
||||
{
|
||||
return .init(self, b, c, d, e, f, g, h)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher of
|
||||
/// the same type, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// - Parameter other: Another publisher of this publisher's type.
|
||||
/// - Returns: A publisher that emits an event when either upstream publisher emits
|
||||
/// an event.
|
||||
public func merge(with other: Self) -> Publishers.MergeMany<Self> {
|
||||
return .init([self, other])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Merge publishers
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the merge function to two upstream
|
||||
/// publishers.
|
||||
public struct Merge<A: Publisher,
|
||||
B: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 2)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge3<A, B, P>
|
||||
{
|
||||
return .init(a, b, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge4<A, B, Z, Y>
|
||||
{
|
||||
return .init(a, b, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge5<A, B, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, z, y, x)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W) -> Publishers.Merge6<A, B, Z, Y, X, W>
|
||||
{
|
||||
return .init(a, b, z, y, x, w)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher,
|
||||
V: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W,
|
||||
_ v: V) -> Publishers.Merge7<A, B, Z, Y, X, W, V>
|
||||
{
|
||||
return .init(a, b, z, y, x, w, v)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher,
|
||||
V: Publisher,
|
||||
U: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W,
|
||||
_ v: V,
|
||||
_ u: U) -> Publishers.Merge8<A, B, Z, Y, X, W, V, U>
|
||||
{
|
||||
return .init(a, b, z, y, x, w, v, u)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to three upstream
|
||||
/// publishers.
|
||||
public struct Merge3<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 3)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge4<A, B, C, P>
|
||||
{
|
||||
return .init(a, b, c, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge5<A, B, C, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge6<A, B, C, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, c, z, y, x)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W) -> Publishers.Merge7<A, B, C, Z, Y, X, W>
|
||||
{
|
||||
return .init(a, b, c, z, y, x, w)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher,
|
||||
V: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W,
|
||||
_ v: V) -> Publishers.Merge8<A, B, C, Z, Y, X, W, V>
|
||||
{
|
||||
return .init(a, b, c, z, y, x, w, v)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to four upstream
|
||||
/// publishers.
|
||||
public struct Merge4<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 4)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge5<A, B, C, D, P>
|
||||
{
|
||||
return .init(a, b, c, d, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge6<A, B, C, D, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, d, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge7<A, B, C, D, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, c, d, z, y, x)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher,
|
||||
W: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X,
|
||||
_ w: W) -> Publishers.Merge8<A, B, C, D, Z, Y, X, W>
|
||||
{
|
||||
return .init(a, b, c, d, z, y, x, w)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to five upstream
|
||||
/// publishers.
|
||||
public struct Merge5<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 5)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge6<A, B, C, D, E, P>
|
||||
{
|
||||
return .init(a, b, c, d, e, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge7<A, B, C, D, E, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, d, e, z, y)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher,
|
||||
X: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y,
|
||||
_ x: X) -> Publishers.Merge8<A, B, C, D, E, Z, Y, X>
|
||||
{
|
||||
return .init(a, b, c, d, e, z, y, x)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to six upstream
|
||||
/// publishers.
|
||||
public struct Merge6<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
self.f = f
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 6)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
f.subscribe(Merged.Side(index: 5, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge7<A, B, C, D, E, F, P>
|
||||
{
|
||||
return .init(a, b, c, d, e, f, other)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
Z: Publisher,
|
||||
Y: Publisher
|
||||
>(with z: Z,
|
||||
_ y: Y) -> Publishers.Merge8<A, B, C, D, E, F, Z, Y>
|
||||
{
|
||||
return .init(a, b, c, d, e, f, z, y)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to seven upstream
|
||||
/// publishers.
|
||||
public struct Merge7<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public let g: G
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
self.f = f
|
||||
self.g = g
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 7)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
f.subscribe(Merged.Side(index: 5, merger: merged))
|
||||
g.subscribe(Merged.Side(index: 6, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge<
|
||||
P: Publisher
|
||||
>(with other: P) -> Publishers.Merge8<A, B, C, D, E, F, G, P>
|
||||
{
|
||||
return .init(a, b, c, d, e, f, g, other)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the merge function to eight upstream
|
||||
/// publishers.
|
||||
public struct Merge8<A: Publisher,
|
||||
B: Publisher,
|
||||
C: Publisher,
|
||||
D: Publisher,
|
||||
E: Publisher,
|
||||
F: Publisher,
|
||||
G: Publisher,
|
||||
H: Publisher>: Publisher
|
||||
where A.Failure == B.Failure, A.Output == B.Output,
|
||||
B.Failure == C.Failure, B.Output == C.Output,
|
||||
C.Failure == D.Failure, C.Output == D.Output,
|
||||
D.Failure == E.Failure, D.Output == E.Output,
|
||||
E.Failure == F.Failure, E.Output == F.Output,
|
||||
F.Failure == G.Failure, F.Output == G.Output,
|
||||
G.Failure == H.Failure, G.Output == H.Output
|
||||
{
|
||||
public typealias Output = A.Output
|
||||
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public let e: E
|
||||
|
||||
public let f: F
|
||||
|
||||
public let g: G
|
||||
|
||||
public let h: H
|
||||
|
||||
public init(
|
||||
_ a: A,
|
||||
_ b: B,
|
||||
_ c: C,
|
||||
_ d: D,
|
||||
_ e: E,
|
||||
_ f: F,
|
||||
_ g: G,
|
||||
_ h: H
|
||||
) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.e = e
|
||||
self.f = f
|
||||
self.g = g
|
||||
self.h = h
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where A.Failure == Downstream.Failure,
|
||||
A.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: 8)
|
||||
a.subscribe(Merged.Side(index: 0, merger: merged))
|
||||
b.subscribe(Merged.Side(index: 1, merger: merged))
|
||||
c.subscribe(Merged.Side(index: 2, merger: merged))
|
||||
d.subscribe(Merged.Side(index: 3, merger: merged))
|
||||
e.subscribe(Merged.Side(index: 4, merger: merged))
|
||||
f.subscribe(Merged.Side(index: 5, merger: merged))
|
||||
g.subscribe(Merged.Side(index: 6, merger: merged))
|
||||
h.subscribe(Merged.Side(index: 7, merger: merged))
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct MergeMany<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let publishers: [Upstream]
|
||||
|
||||
public init(_ upstream: Upstream...) {
|
||||
self.publishers = upstream
|
||||
}
|
||||
|
||||
public init<UpstreamPublishers: Swift.Sequence>(_ upstream: UpstreamPublishers)
|
||||
where Upstream == UpstreamPublishers.Element
|
||||
{
|
||||
publishers = Array(upstream)
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: publishers.count)
|
||||
for (i, upstream) in publishers.enumerated() {
|
||||
upstream.subscribe(Merged.Side(index: i, merger: merged))
|
||||
}
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge(with other: Upstream) -> Publishers.MergeMany<Upstream> {
|
||||
var newPublishers = publishers
|
||||
newPublishers.append(other)
|
||||
return .init(newPublishers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable conformances
|
||||
|
||||
extension Publishers.Merge: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable {}
|
||||
|
||||
extension Publishers.Merge3: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable {}
|
||||
|
||||
extension Publishers.Merge4: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable {}
|
||||
|
||||
extension Publishers.Merge5: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable {}
|
||||
|
||||
extension Publishers.Merge6: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable,
|
||||
F: Equatable {}
|
||||
|
||||
extension Publishers.Merge7: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable,
|
||||
F: Equatable,
|
||||
G: Equatable {}
|
||||
|
||||
extension Publishers.Merge8: Equatable
|
||||
where
|
||||
A: Equatable,
|
||||
B: Equatable,
|
||||
C: Equatable,
|
||||
D: Equatable,
|
||||
E: Equatable,
|
||||
F: Equatable,
|
||||
G: Equatable,
|
||||
H: Equatable {}
|
||||
|
||||
extension Publishers.MergeMany: Equatable
|
||||
where
|
||||
Upstream: Equatable {}
|
||||
@@ -25,8 +25,8 @@ public struct Just<Output>: Publisher {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubscriberType.Input == Output, SubscriberType.Failure == Never
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
subscriber.receive(subscription: Inner(value: output, downstream: subscriber))
|
||||
}
|
||||
@@ -249,35 +249,65 @@ extension Just {
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(Result { try nextPartialResult(initialResult, output) })
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
public func prepend(_ elements: Output...) -> Publishers.Sequence<[Output], Never> {
|
||||
return prepend(elements)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
public func prepend<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Sequence<[Output], Never> where Output == Elements.Element {
|
||||
return .init(sequence: elements + [output])
|
||||
}
|
||||
|
||||
public func append(_ elements: Output...) -> Publishers.Sequence<[Output], Never> {
|
||||
return append(elements)
|
||||
}
|
||||
|
||||
public func append<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Sequence<[Output], Never> where Output == Elements.Element {
|
||||
return .init(sequence: [output] + elements)
|
||||
}
|
||||
}
|
||||
|
||||
extension Just {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
private var downstream: Downstream?
|
||||
private let value: Output
|
||||
|
||||
fileprivate init(value: Output, downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
_ = downstream.receive(value)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
var description: String { return "Just" }
|
||||
func cancel() {
|
||||
downstream = nil
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
var description: String { return "Just" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(value))
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// OperatorSubscription.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 26.06.2019.
|
||||
//
|
||||
|
||||
internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
|
||||
internal var downstream: Downstream?
|
||||
internal var upstreamSubscription: Subscription?
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
internal init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
downstream = nil
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ extension Optional {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
if let output = output {
|
||||
subscriber.receive(subscription: Inner(value: output,
|
||||
@@ -74,34 +74,44 @@ extension Optional {
|
||||
#endif
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
extension Optional.OCombine {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Wrapped
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
private var downstream: Downstream?
|
||||
private let output: Wrapped
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
init(value: Wrapped, downstream: Downstream) {
|
||||
self.output = value
|
||||
self.downstream = downstream
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
var description: String { return "Optional" }
|
||||
func cancel() {
|
||||
downstream = nil
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
var description: String { return "Optional" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +148,7 @@ extension Optional.OCombine.Publisher {
|
||||
}
|
||||
|
||||
public func collect() -> Optional<[Output]>.OCombine.Publisher {
|
||||
return .init(self.output.map { [$0] })
|
||||
return .init(self.output.map { [$0] } ?? [])
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
@@ -229,7 +239,9 @@ extension Optional.OCombine.Publisher {
|
||||
|
||||
// I don't know why, but Combine has this precondition
|
||||
precondition(range.upperBound < .max - 1)
|
||||
return .init(output.flatMap { range.contains(0) ? $0 : nil })
|
||||
return .init(
|
||||
output.flatMap { (range.lowerBound == 0 && range.upperBound != 0) ? $0 : nil }
|
||||
)
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// Publishers.AllSatisfy.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
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.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element.
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
/// - Returns: A publisher that publishes a Boolean value that indicates whether
|
||||
/// all received elements pass a given predicate.
|
||||
public func allSatisfy(
|
||||
_ predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.AllSatisfy<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// - 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.
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryAllSatisfy<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes a single Boolean value that indicates whether
|
||||
/// all received elements pass a given predicate.
|
||||
public struct AllSatisfy<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that evaluates each received element.
|
||||
///
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and finish.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes a single Boolean value that indicates whether
|
||||
/// all received elements pass a given error-throwing predicate.
|
||||
public struct TryAllSatisfy<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// 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.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Error, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.AllSatisfy {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Downstream.Input == Output, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: true, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if !reduce(newValue) {
|
||||
result = false
|
||||
return .finished
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "AllSatisfy" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryAllSatisfy {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: true, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if try !reduce(newValue) {
|
||||
result = false
|
||||
return .finished
|
||||
}
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "TryAllSatisfy" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Publishers.AssertNoFailure.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes
|
||||
/// all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but
|
||||
/// do not impact performance of shipping code.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - prefix: A string used at the beginning of the fatal error message.
|
||||
/// - file: A filename used in the error message. This defaults to `#file`.
|
||||
/// - line: A line number used in the error message. This defaults to `#line`.
|
||||
/// - Returns: A publisher that raises a fatal error when its upstream publisher
|
||||
/// fails.
|
||||
public func assertNoFailure(_ prefix: String = "",
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) -> Publishers.AssertNoFailure<Self> {
|
||||
return .init(upstream: self, prefix: prefix, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that raises a fatal error upon receiving any failure, and otherwise
|
||||
/// republishes all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but
|
||||
/// do not impact performance of shipping code.
|
||||
public struct AssertNoFailure<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The string used at the beginning of the fatal error message.
|
||||
public let prefix: String
|
||||
|
||||
/// The filename used in the error message.
|
||||
public let file: StaticString
|
||||
|
||||
/// The line number used in the error message.
|
||||
public let line: UInt
|
||||
|
||||
public init(upstream: Upstream, prefix: String, file: StaticString, line: UInt) {
|
||||
self.upstream = upstream
|
||||
self.prefix = prefix
|
||||
self.file = file
|
||||
self.line = line
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber,
|
||||
prefix: prefix,
|
||||
file: file,
|
||||
line: line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.AssertNoFailure {
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Never
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let prefix: String
|
||||
|
||||
private let file: StaticString
|
||||
|
||||
private let line: UInt
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(downstream: Downstream, prefix: String, file: StaticString, line: UInt) {
|
||||
self.downstream = downstream
|
||||
self.prefix = prefix
|
||||
self.file = file
|
||||
self.line = line
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
let prefix = self.prefix.isEmpty ? "" : self.prefix + ": "
|
||||
fatalError("\(prefix)\(error)", file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "AssertNoFailure" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("file", file),
|
||||
("line", line),
|
||||
("prefix", prefix)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// Publishers.Autoconnect.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
extension ConnectablePublisher {
|
||||
|
||||
/// Automates the process of connecting or disconnecting from this connectable
|
||||
/// publisher.
|
||||
///
|
||||
/// Use `autoconnect()` to simplify working with `ConnectablePublisher` instances,
|
||||
/// such as those created with `makeConnectable()`.
|
||||
///
|
||||
/// let autoconnectedPublisher = somePublisher
|
||||
/// .makeConnectable()
|
||||
/// .autoconnect()
|
||||
/// .subscribe(someSubscriber)
|
||||
///
|
||||
/// - Returns: A publisher which automatically connects to its upstream connectable
|
||||
/// publisher.
|
||||
public func autoconnect() -> Publishers.Autoconnect<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that automatically connects and disconnects from this connectable
|
||||
/// publisher.
|
||||
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
|
||||
|
||||
private enum State {
|
||||
case disconnected
|
||||
case connected(refcount: Int, connection: Cancellable)
|
||||
}
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public final let upstream: Upstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state = State.disconnected
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(parent: self, downstream: subscriber)
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .connected(refcount, connection):
|
||||
state = .connected(refcount: refcount + 1, connection: connection)
|
||||
lock.unlock()
|
||||
upstream.subscribe(inner)
|
||||
case .disconnected:
|
||||
lock.unlock()
|
||||
upstream.subscribe(inner)
|
||||
let connection = upstream.connect()
|
||||
lock.lock()
|
||||
state = .connected(refcount: 1, connection: connection)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func willCancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .connected(refcount, connection):
|
||||
if refcount <= 1 {
|
||||
self.state = .disconnected
|
||||
lock.unlock()
|
||||
connection.cancel()
|
||||
} else {
|
||||
state = .connected(refcount: refcount - 1, connection: connection)
|
||||
lock.unlock()
|
||||
}
|
||||
case .disconnected:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Autoconnect {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
fileprivate let combineIdentifier = CombineIdentifier()
|
||||
|
||||
private let parent: Publishers.Autoconnect<Upstream>
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
fileprivate init(parent: Publishers.Autoconnect<Upstream>,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
let sideEffectSubscription = SideEffectSubscription(subscription,
|
||||
parent: parent)
|
||||
downstream.receive(subscription: sideEffectSubscription)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
fileprivate var description: String { return "Autoconnect" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent),
|
||||
("downstream", downstream)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private struct SideEffectSubscription
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private let parent: Publishers.Autoconnect<Upstream>
|
||||
|
||||
private let upstreamSubscription: Subscription
|
||||
|
||||
fileprivate init(_ upstreamSubscription: Subscription,
|
||||
parent: Publishers.Autoconnect<Upstream>) {
|
||||
self.parent = parent
|
||||
self.upstreamSubscription = upstreamSubscription
|
||||
}
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription.request(demand)
|
||||
}
|
||||
|
||||
fileprivate func cancel() {
|
||||
parent.willCancel()
|
||||
upstreamSubscription.cancel()
|
||||
}
|
||||
|
||||
fileprivate var combineIdentifier: CombineIdentifier {
|
||||
return upstreamSubscription.combineIdentifier
|
||||
}
|
||||
|
||||
fileprivate var description: String {
|
||||
return String(describing: upstreamSubscription)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any {
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// Publishers.Breakpoint.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
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.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveSubscription: A closure that executes when when the publisher receives
|
||||
/// a subscription. Return `true` from this closure to raise `SIGTRAP`, or `false`
|
||||
/// to continue.
|
||||
/// - receiveOutput: A closure that executes when when the publisher receives
|
||||
/// a value. Return `true` from this closure to raise `SIGTRAP`, or `false`
|
||||
/// to continue.
|
||||
/// - receiveCompletion: A closure that executes when when the publisher receives
|
||||
/// a completion. Return `true` from this closure to raise `SIGTRAP`, or `false`
|
||||
/// to continue.
|
||||
/// - Returns: A publisher that raises a debugger signal when one of the provided
|
||||
/// closures returns `true`.
|
||||
public func breakpoint(
|
||||
receiveSubscription: ((Subscription) -> Bool)? = nil,
|
||||
receiveOutput: ((Output) -> Bool)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Bool)? = nil
|
||||
) -> Publishers.Breakpoint<Self> {
|
||||
return .init(upstream: self,
|
||||
receiveSubscription: receiveSubscription,
|
||||
receiveOutput: receiveOutput,
|
||||
receiveCompletion: receiveCompletion)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// - Returns: A publisher that raises a debugger signal upon receiving a failure.
|
||||
public func breakpointOnError() -> Publishers.Breakpoint<Self> {
|
||||
return breakpoint { completion in
|
||||
switch completion {
|
||||
case .finished:
|
||||
return false
|
||||
case .failure:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that 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.
|
||||
public struct Breakpoint<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
|
||||
|
||||
/// A closure that executes when the publisher receives a subscription, and can
|
||||
/// raise a debugger signal by returning a `true` Boolean value.
|
||||
public let receiveSubscription: ((Subscription) -> Bool)?
|
||||
|
||||
/// A closure that executes when the publisher receives output from the upstream
|
||||
/// publisher, and can raise a debugger signal by returning a `true` Boolean
|
||||
/// value.
|
||||
public let receiveOutput: ((Upstream.Output) -> Bool)?
|
||||
|
||||
/// A closure that executes when the publisher receives completion, and can raise
|
||||
/// a debugger signal by returning a `true` Boolean value.
|
||||
public let receiveCompletion:
|
||||
((Subscribers.Completion<Upstream.Failure>) -> Bool)?
|
||||
|
||||
/// Creates a breakpoint publisher with the provided upstream publisher and
|
||||
/// breakpoint-raising closures.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher from which this publisher receives elements.
|
||||
/// - receiveSubscription: A closure that executes when the publisher receives
|
||||
/// a subscription, and can raise a debugger signal by returning a `true`
|
||||
/// Boolean value.
|
||||
/// - receiveOutput: A closure that executes when the publisher receives output
|
||||
/// from the upstream publisher, and can raise a debugger signal by returning
|
||||
/// a `true` Boolean value.
|
||||
/// - receiveCompletion: A closure that executes when the publisher receives
|
||||
/// completion, and can raise a debugger signal by returning a `true` Boolean
|
||||
/// value.
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
receiveSubscription: ((Subscription) -> Bool)? = nil,
|
||||
receiveOutput: ((Upstream.Output) -> Bool)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Bool)? = nil
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.receiveSubscription = receiveSubscription
|
||||
self.receiveOutput = receiveOutput
|
||||
self.receiveCompletion = receiveCompletion
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Breakpoint {
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
private let breakpoint: Publishers.Breakpoint<Upstream>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(_ breakpoint: Publishers.Breakpoint<Upstream>,
|
||||
downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
self.breakpoint = breakpoint
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
if breakpoint.receiveSubscription?(subscription) == true {
|
||||
__stopInDebugger()
|
||||
}
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
if breakpoint.receiveOutput?(input) == true {
|
||||
__stopInDebugger()
|
||||
}
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if breakpoint.receiveCompletion?(completion) == true {
|
||||
__stopInDebugger()
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Breakpoint" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(
|
||||
("upstream", breakpoint.upstream)
|
||||
)
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Publishers.Collect.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Collects all received elements, and emits a single array of the collection when
|
||||
/// 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.
|
||||
///
|
||||
/// - Returns: A publisher that collects all received items and returns them as
|
||||
/// an array upon completion.
|
||||
public func collect() -> Publishers.Collect<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that buffers items.
|
||||
public struct Collect<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = [Upstream.Output]
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Downstream.Input == [Upstream.Output]
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Collect: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Collect {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
[Upstream.Output],
|
||||
Upstream.Failure,
|
||||
Void>
|
||||
where Downstream.Input == [Upstream.Output],
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: [], reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result!.append(newValue)
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "Collect"
|
||||
}
|
||||
|
||||
override var customMirror: Mirror {
|
||||
let children: CollectionOfOne<Mirror.Child> = .init(("count", result!.count))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// Publishers.CollectByCount.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Collects up to the specified number of elements, and then emits a single array of
|
||||
/// the collection.
|
||||
///
|
||||
/// If the upstream publisher finishes before filling the buffer, this publisher sends
|
||||
/// an array of all the items it has received. This may be fewer than `count`
|
||||
/// elements.
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
/// Note: When this publisher receives a request for `.max(n)` elements, it requests
|
||||
/// `.max(count * n)` from the upstream publisher.
|
||||
///
|
||||
/// - Parameter count: The maximum number of received elements to buffer before
|
||||
/// publishing.
|
||||
/// - Returns: A publisher that collects up to the specified number of elements, and
|
||||
/// then publishes them as an array.
|
||||
public func collect(_ count: Int) -> Publishers.CollectByCount<Self> {
|
||||
return .init(upstream: self, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that buffers a maximum number of items.
|
||||
public struct CollectByCount<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = [Upstream.Output]
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The maximum number of received elements to buffer before publishing.
|
||||
public let count: Int
|
||||
|
||||
public init(upstream: Upstream, count: Int) {
|
||||
self.upstream = upstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, count: count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.CollectByCount: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.CollectByCount {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == [Upstream.Output],
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let count: Int
|
||||
|
||||
private var buffer: [Input] = []
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
private var finished = false
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
init(downstream: Downstream, count: Int) {
|
||||
self.downstream = downstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if subscription == nil {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
buffer.append(input)
|
||||
guard buffer.count == count else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let output = self.buffer
|
||||
self.buffer = []
|
||||
lock.unlock()
|
||||
return downstream.receive(output) * count
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
subscription = nil
|
||||
finished = true
|
||||
switch completion {
|
||||
case .finished:
|
||||
if buffer.isEmpty {
|
||||
lock.unlock()
|
||||
} else {
|
||||
let buffer = self.buffer
|
||||
self.buffer = []
|
||||
lock.unlock()
|
||||
_ = downstream.receive(buffer)
|
||||
}
|
||||
case .failure:
|
||||
buffer = []
|
||||
lock.unlock()
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
if let subscription = self.subscription {
|
||||
lock.unlock()
|
||||
subscription.request(demand * count)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
if let subscription = self.subscription {
|
||||
buffer = []
|
||||
finished = true
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "CollectByCount" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("upstreamSubscription", subscription as Any),
|
||||
("buffer", buffer),
|
||||
("count", count)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// Publishers.CompactMap.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.07.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
///
|
||||
/// If the closure throws an error, the publisher cancels the upstream and sends
|
||||
/// the thrown error to the downstream receiver as a `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.
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
|
||||
return .init(upstream: self, transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.CompactMap {
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult?
|
||||
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
|
||||
return .init(upstream: upstream,
|
||||
transform: { self.transform($0).flatMap(transform) })
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult
|
||||
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
|
||||
return .init(upstream: upstream,
|
||||
transform: { self.transform($0).map(transform) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCompactMap {
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.TryCompactMap<Upstream, ElementOfResult> {
|
||||
return .init(upstream: upstream,
|
||||
transform: { try self.transform($0).flatMap(transform) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes all non-`nil` results of calling a closure
|
||||
/// with each received element.
|
||||
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives values from the upstream publisher
|
||||
/// and returns optional values.
|
||||
public let transform: (Upstream.Output) -> Output?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) -> Output?) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes all non-`nil` results of calling an error-throwing
|
||||
/// closure with each received element.
|
||||
public struct TryCompactMap<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// An error-throwing closure that receives values from the upstream publisher
|
||||
/// and returns optional values.
|
||||
///
|
||||
/// If this closure throws an error, the publisher fails.
|
||||
public let transform: (Upstream.Output) throws -> Output?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) throws -> Output?) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Output?, Downstream.Failure> {
|
||||
return .continue(filter(newValue))
|
||||
}
|
||||
|
||||
override var description: String { return "CompactMap" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCompactMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(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> {
|
||||
do {
|
||||
return try .continue(filter(newValue))
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryCompactMap" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// Publishers.Comparison.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Ilija Puaca on 22/7/19.
|
||||
//
|
||||
|
||||
extension Publisher where Output: Comparable {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min() -> Publishers.Comparison<Self> {
|
||||
return max(by: >)
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max() -> Publishers.Comparison<Self> {
|
||||
return max(by: <)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// 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.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.Comparison<Self> {
|
||||
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.
|
||||
///
|
||||
/// 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`.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
return tryMax(by: { try areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided ordering closure.
|
||||
///
|
||||
/// 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.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.Comparison<Self> {
|
||||
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.
|
||||
///
|
||||
/// 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`.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is
|
||||
/// in increasing order from the previously-published item.
|
||||
public struct Comparison<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in
|
||||
/// increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) -> Bool
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.areInIncreasingOrder = areInIncreasingOrder
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
areInIncreasingOrder: areInIncreasingOrder)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is
|
||||
/// in increasing order from the previously-published item, and fails if the ordering
|
||||
/// logic throws an error.
|
||||
public struct TryComparison<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in
|
||||
/// increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
areInIncreasingOrder:
|
||||
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.areInIncreasingOrder = areInIncreasingOrder
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
areInIncreasingOrder: areInIncreasingOrder)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Comparison {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output, Upstream.Output) -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
|
||||
) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if let result = self.result {
|
||||
if reduce(result, newValue) {
|
||||
self.result = newValue
|
||||
}
|
||||
} else {
|
||||
self.result = newValue
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "Comparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryComparison {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output, Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
areInIncreasingOrder:
|
||||
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if let result = self.result {
|
||||
if try reduce(result, newValue) {
|
||||
self.result = newValue
|
||||
}
|
||||
} else {
|
||||
self.result = newValue
|
||||
}
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "TryComparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
//
|
||||
// Publishers.Concatenate.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Prefixes a `Publisher`'s output with the specified sequence.
|
||||
///
|
||||
/// - Parameter elements: The elements to publish before this publisher’s elements.
|
||||
/// - Returns: A publisher that prefixes the specified elements prior to this
|
||||
/// publisher’s elements.
|
||||
public func prepend(
|
||||
_ elements: Output...
|
||||
) -> Publishers.Concatenate<Publishers.Sequence<[Output], Failure>, Self> {
|
||||
return prepend(elements)
|
||||
}
|
||||
|
||||
/// Prefixes a `Publisher`'s output with the specified sequence.
|
||||
///
|
||||
/// - Parameter elements: A sequence of elements to publish before this publisher’s
|
||||
/// elements.
|
||||
/// - Returns: A publisher that prefixes the sequence of elements prior to this
|
||||
/// publisher’s elements.
|
||||
public func prepend<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Concatenate<Publishers.Sequence<Elements, Failure>, Self>
|
||||
where Output == Elements.Element
|
||||
{
|
||||
return prepend(.init(sequence: elements))
|
||||
}
|
||||
|
||||
/// Prefixes this publisher’s output with the elements emitted by the given publisher.
|
||||
///
|
||||
/// The resulting publisher doesn’t emit any elements until the prefixing publisher
|
||||
/// finishes.
|
||||
///
|
||||
/// - Parameter publisher: The prefixing publisher.
|
||||
/// - Returns: A publisher that prefixes the prefixing publisher’s elements prior to
|
||||
/// this publisher’s elements.
|
||||
public func prepend<Prefix: Publisher>(
|
||||
_ publisher: Prefix
|
||||
) -> Publishers.Concatenate<Prefix, Self>
|
||||
where Failure == Prefix.Failure, Output == Prefix.Output
|
||||
{
|
||||
return .init(prefix: publisher, suffix: self)
|
||||
}
|
||||
|
||||
/// Append a `Publisher`'s output with the specified sequence.
|
||||
public func append(
|
||||
_ elements: Output...
|
||||
) -> Publishers.Concatenate<Self, Publishers.Sequence<[Output], Failure>> {
|
||||
return append(elements)
|
||||
}
|
||||
|
||||
/// Appends a `Publisher`'s output with the specified sequence.
|
||||
public func append<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Concatenate<Self, Publishers.Sequence<Elements, Failure>>
|
||||
where Output == Elements.Element
|
||||
{
|
||||
return append(.init(sequence: elements))
|
||||
}
|
||||
|
||||
/// Appends this publisher’s output 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.
|
||||
///
|
||||
/// - Parameter publisher: The appending publisher.
|
||||
/// - Returns: A publisher that appends the appending publisher’s elements after this
|
||||
/// publisher’s elements.
|
||||
public func append<Suffix: Publisher>(
|
||||
_ publisher: Suffix
|
||||
) -> Publishers.Concatenate<Self, Suffix>
|
||||
where Suffix.Failure == Failure, Suffix.Output == Output
|
||||
{
|
||||
return .init(prefix: self, suffix: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that emits all of one publisher’s elements before those from anothe
|
||||
/// publisher.
|
||||
public struct Concatenate<Prefix: Publisher, Suffix: Publisher>: Publisher
|
||||
where Prefix.Failure == Suffix.Failure, Prefix.Output == Suffix.Output
|
||||
{
|
||||
public typealias Output = Suffix.Output
|
||||
|
||||
public typealias Failure = Suffix.Failure
|
||||
|
||||
/// The publisher to republish, in its entirety, before republishing elements from
|
||||
/// `suffix`.
|
||||
public let prefix: Prefix
|
||||
|
||||
/// The publisher to republish only after `prefix` finishes.
|
||||
public let suffix: Suffix
|
||||
|
||||
public init(prefix: Prefix, suffix: Suffix) {
|
||||
self.prefix = prefix
|
||||
self.suffix = suffix
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Suffix.Failure == Downstream.Failure, Suffix.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, suffix: suffix)
|
||||
prefix.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate: Equatable where Prefix: Equatable, Suffix: Equatable {}
|
||||
|
||||
extension Publishers.Concatenate {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Suffix.Output, Downstream.Failure == Suffix.Failure
|
||||
{
|
||||
typealias Input = Suffix.Output
|
||||
|
||||
typealias Failure = Suffix.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let suffix: Suffix
|
||||
|
||||
private var prefixFinished = false
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var upstream: Subscription?
|
||||
|
||||
private var expectedSubscriptions = 2
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, suffix: Suffix) {
|
||||
self.downstream = downstream
|
||||
self.suffix = suffix
|
||||
}
|
||||
|
||||
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 {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard let subscription = upstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstream = nil
|
||||
lock.unlock()
|
||||
subscription.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)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// Publishers.Contains.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
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.
|
||||
///
|
||||
/// - Parameter output: An element to match against.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func contains(_ output: Output) -> Publishers.Contains<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// - 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 publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func contains(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.ContainsWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes a Boolean value upon receiving an element that satisfies
|
||||
/// the throwing predicate closure.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// - 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 publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func tryContains(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryContainsWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that emits a Boolean value when a specified element is received from
|
||||
/// its upstream publisher.
|
||||
public struct Contains<Upstream: Publisher>: Publisher
|
||||
where Upstream.Output: Equatable
|
||||
{
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The element to scan for in the upstream publisher.
|
||||
public let output: Upstream.Output
|
||||
|
||||
public init(upstream: Upstream, output: Upstream.Output) {
|
||||
self.upstream = upstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, output: output))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that emits a Boolean value upon receiving an element that satisfies
|
||||
/// the predicate closure.
|
||||
public struct ContainsWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that determines whether the publisher should consider an element
|
||||
/// as a match.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that emits a Boolean value upon receiving an element that satisfies
|
||||
/// the throwing predicate closure.
|
||||
public struct TryContainsWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether this publisher should
|
||||
/// emit a `true` element.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Error, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Contains {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream, Upstream.Output, Bool, Upstream.Failure, Void>
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
private let output: Upstream.Output
|
||||
|
||||
fileprivate init(downstream: Downstream, output: Upstream.Output) {
|
||||
self.output = output
|
||||
super.init(downstream: downstream, initial: false, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if newValue == output {
|
||||
result = true
|
||||
return .finished
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Contains" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Contains : Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.ContainsWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output, Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: false, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if reduce(newValue) {
|
||||
result = true
|
||||
return .finished
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "ContainsWhere" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryContainsWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output, Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Downstream.Failure == Error, Downstream.Input == Bool
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: false, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if try reduce(newValue) {
|
||||
result = true
|
||||
return .finished
|
||||
}
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "TryContainsWhere" }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,17 @@
|
||||
// Created by Joseph Spadafora on 6/25/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self> {
|
||||
return Publishers.Count(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the number of elements received
|
||||
@@ -33,62 +44,34 @@ extension Publishers {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Upstream.Failure == SubscriberType.Failure,
|
||||
SubscriberType.Input == Output
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Downstream.Input == Output
|
||||
{
|
||||
let count = _Count<Upstream, SubscriberType>(downstream: subscriber)
|
||||
upstream.subscribe(count)
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
extension Publishers.Count: Equatable where Upstream: Equatable {}
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self> {
|
||||
return Publishers.Count(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
private final class _Count<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Downstream.Input == Int,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Output = Int
|
||||
typealias Failure = Downstream.Failure
|
||||
|
||||
private var _count = 0
|
||||
|
||||
var description: String { return "Count" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
upstreamSubscription?.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
_count += 1
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if case .finished = completion {
|
||||
_ = downstream.receive(_count)
|
||||
extension Publishers.Count {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream, Upstream.Output, Int, Failure, Void>
|
||||
where Downstream.Input == Int,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: 0, reduce: ())
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result! += 1
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Count" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
//
|
||||
// Publishers.Decode.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/21/19.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Decode<Upstream, Output, Coder>: Publisher
|
||||
where Upstream: Publisher,
|
||||
Output: Decodable,
|
||||
Coder: TopLevelDecoder,
|
||||
Upstream.Output == Coder.Input
|
||||
{
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
private let _decoder: Coder
|
||||
|
||||
public init(upstream: Upstream, decoder: Coder) {
|
||||
self.upstream = upstream
|
||||
self._decoder = decoder
|
||||
}
|
||||
|
||||
/// 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<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
let decodeSubscriber = _Decode<Upstream, SubscriberType, Coder>(
|
||||
downstream: subscriber,
|
||||
decoder: _decoder
|
||||
)
|
||||
upstream.subscribe(decodeSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class _Decode<Upstream: Publisher,
|
||||
Downstream: Subscriber,
|
||||
Coder: TopLevelDecoder>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Downstream.Input: Decodable,
|
||||
Coder.Input == Upstream.Output,
|
||||
Downstream.Failure == Error {
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Output = Downstream.Input
|
||||
|
||||
private let _decoder: Coder
|
||||
|
||||
var description: String { return "Decode" }
|
||||
|
||||
init(downstream: Downstream, decoder: Coder) {
|
||||
self._decoder = decoder
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
do {
|
||||
let value = try _decoder.decode(Downstream.Input.self, from: input)
|
||||
return downstream.receive(value)
|
||||
} catch {
|
||||
downstream.receive(completion: .failure(error))
|
||||
cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
public func decode<Item: Decodable,
|
||||
Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
) -> Publishers.Decode<Self, Item, Coder>
|
||||
where Self.Output == Coder.Input
|
||||
{
|
||||
return Publishers.Decode(upstream: self, decoder: decoder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
//
|
||||
// Publishers.Delay.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Евгений Богомолов on 07/09/2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Delays delivery of all output to the downstream receiver by a specified amount
|
||||
/// of time on a particular scheduler.
|
||||
///
|
||||
/// The delay affects the delivery of elements and completion, but not of the original
|
||||
/// subscription.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The amount of time to delay.
|
||||
/// - tolerance: The allowed tolerance in firing delayed events.
|
||||
/// - scheduler: The scheduler to deliver the delayed events.
|
||||
/// - Returns: A publisher that delays delivery of elements and completion to
|
||||
/// the downstream receiver.
|
||||
public func delay<Context: Scheduler>(
|
||||
for interval: Context.SchedulerTimeType.Stride,
|
||||
tolerance: Context.SchedulerTimeType.Stride? = nil,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil
|
||||
) -> Publishers.Delay<Self, Context> {
|
||||
return .init(upstream: self,
|
||||
interval: interval,
|
||||
tolerance: tolerance ?? scheduler.minimumTolerance,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that delays delivery of elements and completion
|
||||
/// to the downstream receiver.
|
||||
public struct Delay<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The amount of time to delay.
|
||||
public let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The allowed tolerance in firing delayed events.
|
||||
public let tolerance: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler to deliver the delayed events.
|
||||
public let scheduler: Context
|
||||
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
tolerance: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil)
|
||||
{
|
||||
self.upstream = upstream
|
||||
self.interval = interval
|
||||
self.tolerance = tolerance
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Delay {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
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 downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(_ publisher: Delay, downstream: Downstream) {
|
||||
state = .ready(publisher, downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
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,
|
||||
work)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(delay, downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(delay, downstream, subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(delay, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
schedule(delay) {
|
||||
self.scheduledReceive(input, downstream: downstream)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
private func scheduledReceive(_ input: Upstream.Output, downstream: Downstream) {
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else {
|
||||
return
|
||||
}
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(newDemand)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(delay, downstream, _) = state else {
|
||||
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()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Publishers.Drop.swift
|
||||
//
|
||||
//
|
||||
// Created by Sven Weidauer on 03.10.2019.
|
||||
//
|
||||
|
||||
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.
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self> {
|
||||
return .init(upstream: self, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
/// A publisher that omits a specified number of elements before republishing
|
||||
/// later elements.
|
||||
public struct Drop<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 number of elements to drop.
|
||||
public let count: Int
|
||||
|
||||
public init(upstream: Upstream, count: Int) {
|
||||
self.upstream = upstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, count: count)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Drop: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Drop {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
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
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
private var count: Int
|
||||
|
||||
fileprivate init(downstream: Downstream, count: Int) {
|
||||
self.downstream = downstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard self.subscription == nil else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
let demandToRequestFromUpstream = pendingDemand + count
|
||||
lock.unlock()
|
||||
if demandToRequestFromUpstream > 0 {
|
||||
subscription.request(demandToRequestFromUpstream)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
// Combine doesn't lock here!
|
||||
if count > 0 {
|
||||
count -= 1
|
||||
return .none
|
||||
}
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
// Combine doesn't lock here!
|
||||
subscription = nil
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let subscription = self.subscription else {
|
||||
self.pendingDemand += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
// Combine doesn't lock here!
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
}
|
||||
|
||||
var description: String { return "Drop" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
//
|
||||
// Publishers.DropUntilOutput.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ignores elements from the upstream publisher until it receives an element from
|
||||
/// a second publisher.
|
||||
///
|
||||
/// This publisher requests a single value from the upstream publisher, and it ignores
|
||||
/// (drops) all elements from that publisher until the upstream publisher produces
|
||||
/// a value. After the `other` publisher produces an element, this publisher cancels
|
||||
/// its subscription to the `other` publisher, and allows events from the `upstream`
|
||||
/// publisher to pass through.
|
||||
/// After this publisher receives a subscription from the upstream publisher, it
|
||||
/// passes through backpressure requests from downstream to the upstream publisher.
|
||||
/// If the upstream publisher acts on those requests before the other publisher
|
||||
/// produces an item, this publisher drops the elements it receives from the upstream
|
||||
/// publisher.
|
||||
///
|
||||
/// - Parameter publisher: A publisher to monitor for its first emitted element.
|
||||
/// - Returns: A publisher that drops elements from the upstream publisher until the
|
||||
/// `other` publisher produces a value.
|
||||
public func drop<Other: Publisher>(
|
||||
untilOutputFrom publisher: Other
|
||||
) -> Publishers.DropUntilOutput<Self, Other> where Failure == Other.Failure {
|
||||
return .init(upstream: self, other: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that ignores elements from the upstream publisher until it receives
|
||||
/// an element from second publisher.
|
||||
public struct DropUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher
|
||||
where Upstream.Failure == Other.Failure
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A publisher to monitor for its first emitted element.
|
||||
public let other: Other
|
||||
|
||||
/// Creates a publisher that ignores elements from the upstream publisher until
|
||||
/// it receives an element from another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: A publisher to drop elements from while waiting for another
|
||||
/// publisher to emit elements.
|
||||
/// - other: A publisher to monitor for its first emitted element.
|
||||
public init(upstream: Upstream, other: Other) {
|
||||
self.upstream = upstream
|
||||
self.other = other
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Other.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber)
|
||||
other.subscribe(Inner.OtherSubscriber(inner: inner))
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.DropUntilOutput: Equatable
|
||||
where Upstream: Equatable, Other: Equatable {}
|
||||
|
||||
extension Publishers.DropUntilOutput {
|
||||
fileprivate final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var triggered = false
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
private var otherSubscription: Subscription?
|
||||
|
||||
private var otherFinished = false
|
||||
|
||||
private var cancelled = false
|
||||
|
||||
init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard upstreamSubscription == nil && !cancelled else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = subscription
|
||||
if pendingDemand > 0 {
|
||||
lock.unlock()
|
||||
subscription.request(pendingDemand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if !triggered || cancelled {
|
||||
pendingDemand -= 1
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
if cancelled {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
private func receiveOther(subscription: Subscription) {
|
||||
// Combine doesn't lock here
|
||||
guard otherSubscription == nil else {
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
otherSubscription = subscription
|
||||
subscription.request(.max(1))
|
||||
}
|
||||
|
||||
private func receiveOther(_ input: Other.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
triggered = true
|
||||
otherSubscription = nil
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
private func receiveOther(completion: Subscribers.Completion<Other.Failure>) {
|
||||
lock.lock()
|
||||
if triggered {
|
||||
otherSubscription = nil
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
otherFinished = true
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
self.upstreamSubscription = nil
|
||||
lock.unlock()
|
||||
upstreamSubscription.cancel()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
pendingDemand += demand
|
||||
if let subscription = upstreamSubscription {
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let upstreamSubscription = self.upstreamSubscription
|
||||
let otherSubscription = self.otherSubscription
|
||||
self.upstreamSubscription = nil
|
||||
self.otherSubscription = nil
|
||||
cancelled = true
|
||||
lock.unlock()
|
||||
|
||||
upstreamSubscription?.cancel()
|
||||
otherSubscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "DropUntilOutput" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.DropUntilOutput.Inner {
|
||||
fileprivate struct OtherSubscriber
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
let inner: Publishers.DropUntilOutput<Upstream, Other>.Inner<Downstream>
|
||||
|
||||
var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receiveOther(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Other.Output) -> Subscribers.Demand {
|
||||
return inner.receiveOther(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Other.Failure>) {
|
||||
inner.receiveOther(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "DropUntilOutput" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,40 @@
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Omits elements from the upstream publisher until a given closure returns false,
|
||||
/// before republishing all remaining elements.
|
||||
///
|
||||
/// - 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.
|
||||
/// - Returns: A publisher that skips over elements until the provided closure returns
|
||||
/// `false`.
|
||||
public func drop(
|
||||
while predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.DropWhile<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// - 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.
|
||||
/// - Returns: A publisher that skips over elements until the provided closure returns
|
||||
/// `false`, and then republishes all remaining elements. If the predicate closure
|
||||
/// throws, the publisher fails with an error.
|
||||
public func tryDrop(
|
||||
while predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryDropWhile<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that omits elements from an upstream publisher until a given closure
|
||||
@@ -26,11 +60,10 @@ extension Publishers {
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
|
||||
upstream.subscribe(inner)
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,151 +86,252 @@ extension Publishers {
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, SubscriberType.Failure == Error
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
|
||||
upstream.subscribe(inner)
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscription
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Predicate = (Input) -> Result<Bool, Downstream.Failure>
|
||||
|
||||
/// The predicate is reset to `nil` as soon as it returns `false`.
|
||||
var predicate: Predicate?
|
||||
var isCompleted = false
|
||||
|
||||
init(downstream: Downstream, predicate: @escaping Predicate) {
|
||||
self.predicate = predicate
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
|
||||
guard upstreamSubscription != nil else {
|
||||
return .none
|
||||
}
|
||||
|
||||
guard let predicate = self.predicate else {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
// NOTE: until the predicate returns false, we will ask the upstream publisher
|
||||
// for elements one by one.
|
||||
//
|
||||
// However, IF the downstream requests anything, we accumulate this demand in the
|
||||
// `demand` property so that later we can provide the downstream with the correct
|
||||
// amount of values.
|
||||
//
|
||||
// As soon as the predicate returns false, we switch to the mode where
|
||||
// we just forward all the requests from the downstream to the upstream.
|
||||
switch predicate(input) {
|
||||
case .success(true):
|
||||
return .max(1)
|
||||
case .success(false):
|
||||
// Okay, we hit the first element not satisfying the predicate,
|
||||
// from now on we just republish the values to the downstream.
|
||||
self.predicate = nil
|
||||
return downstream.receive(input)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
isCompleted = true
|
||||
cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
// Don't zero out downstream, that's what Combine does (probably a bug)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.DropWhile {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _DropWhile<Upstream, Downstream>,
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
var description: String { return "DropWhile" }
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isCompleted else {
|
||||
assertionFailure("unreachable")
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var predicate: ((Input) -> Bool)?
|
||||
|
||||
private var dropping = true
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, predicate: @escaping (Input) -> Bool) {
|
||||
self.downstream = downstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = status, let shouldDrop = predicate else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let dropping = self.dropping
|
||||
lock.unlock()
|
||||
|
||||
if dropping {
|
||||
if shouldDrop(input) {
|
||||
return .max(1)
|
||||
} else {
|
||||
lock.lock()
|
||||
self.dropping = false
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
predicate = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
predicate = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "DropWhile" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryDropWhile {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _DropWhile<Upstream, Downstream>,
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
var description: String { return "TryDropWhile" }
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var predicate: ((Input) throws -> Bool)?
|
||||
|
||||
private var dropping = true
|
||||
|
||||
private var finished = false
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Input) throws -> Bool) {
|
||||
self.downstream = downstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status,
|
||||
let shouldDrop = predicate else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let dropping = self.dropping
|
||||
lock.unlock()
|
||||
|
||||
if dropping {
|
||||
do {
|
||||
if try shouldDrop(input) {
|
||||
return .max(1)
|
||||
} else {
|
||||
lock.lock()
|
||||
self.dropping = false
|
||||
lock.unlock()
|
||||
}
|
||||
} catch {
|
||||
lock.lock()
|
||||
status = .terminal
|
||||
predicate = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isCompleted else { return }
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
let wasFinished = finished
|
||||
finished = true
|
||||
lock.unlock()
|
||||
|
||||
if !wasFinished {
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Omits elements from the upstream publisher until a given closure returns false,
|
||||
/// before republishing all remaining elements.
|
||||
///
|
||||
/// - 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.
|
||||
/// - Returns: A publisher that skips over elements until the provided closure returns
|
||||
/// `false`.
|
||||
public func drop(
|
||||
while predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.DropWhile<Self> {
|
||||
return Publishers.DropWhile(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// - 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.
|
||||
/// - Returns: A publisher that skips over elements until the provided closure returns
|
||||
/// `false`, and then republishes all remaining elements. If the predicate closure
|
||||
/// throws, the publisher fails with an error.
|
||||
public func tryDrop(
|
||||
while predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryDropWhile<Self> {
|
||||
return Publishers.TryDropWhile(upstream: self, predicate: predicate)
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
predicate = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "TryDropWhile" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
//
|
||||
// Publishers.Encode.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/22/19.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Encode<Upstream, Coder>: Publisher
|
||||
where Upstream: Publisher,
|
||||
Coder: TopLevelEncoder,
|
||||
Upstream.Output: Encodable
|
||||
{
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Coder.Output
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
private let encoder: Coder
|
||||
|
||||
public init(upstream: Upstream, encoder: Coder) {
|
||||
self.upstream = upstream
|
||||
self.encoder = encoder
|
||||
}
|
||||
|
||||
/// 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<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
let encodeSubscriber = _Encode<Upstream, SubscriberType, Coder>(
|
||||
downstream: subscriber,
|
||||
encoder: encoder
|
||||
)
|
||||
upstream.subscribe(encodeSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class _Encode<Upstream: Publisher,
|
||||
Downstream: Subscriber,
|
||||
Coder: TopLevelEncoder>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Coder.Output == Downstream.Input,
|
||||
Upstream.Output: Encodable,
|
||||
Downstream.Failure == Error {
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Output = Downstream.Input
|
||||
|
||||
private let _encoder: Coder
|
||||
|
||||
var description: String { return "Encode" }
|
||||
|
||||
init(downstream: Downstream, encoder: Coder) {
|
||||
self._encoder = encoder
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
do {
|
||||
let value = try _encoder.encode(input)
|
||||
return downstream.receive(value)
|
||||
} catch {
|
||||
downstream.receive(completion: .failure(error))
|
||||
cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
public func encode<Coder>(
|
||||
encoder: Coder
|
||||
) -> Publishers.Encode<Self, Coder>
|
||||
where Coder: TopLevelEncoder
|
||||
{
|
||||
return Publishers.Encode(upstream: self, encoder: encoder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.Encode.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/22/19.
|
||||
//
|
||||
|
||||
%{
|
||||
instantiations = ['Encode', 'Decode']
|
||||
}%
|
||||
extension Publisher {
|
||||
|
||||
/// Encodes the output from upstream using a specified `TopLevelEncoder`.
|
||||
/// For example, use `JSONEncoder`.
|
||||
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`.
|
||||
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
) -> Publishers.Decode<Self, Item, Coder> where Output == Coder.Input {
|
||||
return .init(upstream: self, decoder: decoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Encode<Upstream: Publisher, Coder: TopLevelEncoder>: Publisher
|
||||
where Upstream.Output: Encodable
|
||||
{
|
||||
public typealias Failure = Error
|
||||
|
||||
public typealias Output = Coder.Output
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
private let _encode: (Upstream.Output) throws -> Output
|
||||
|
||||
public init(upstream: Upstream, encoder: Coder) {
|
||||
self.upstream = upstream
|
||||
self._encode = encoder.encode
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, encode: _encode))
|
||||
}
|
||||
}
|
||||
|
||||
public struct Decode<Upstream: Publisher, Output: Decodable, Coder: TopLevelDecoder>
|
||||
: Publisher
|
||||
where Upstream.Output == Coder.Input
|
||||
{
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
private let _decode: (Upstream.Output) throws -> Output
|
||||
|
||||
public init(upstream: Upstream, decoder: Coder) {
|
||||
self.upstream = upstream
|
||||
self._decode = { try decoder.decode(Output.self, from: $0) }
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, decode: _decode))
|
||||
}
|
||||
}
|
||||
}
|
||||
% for instantiation in instantiations:
|
||||
|
||||
extension Publishers.${instantiation} {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
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
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let ${instantiation.lower()}: (Upstream.Output) throws -> Output
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
${instantiation.lower()}: @escaping (Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.${instantiation.lower()} = ${instantiation.lower()}
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
if finished || self.subscription != nil {
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if finished { return .none }
|
||||
do {
|
||||
return try downstream.receive(${instantiation.lower()}(input))
|
||||
} catch {
|
||||
finished = true
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if finished { return }
|
||||
finished = true
|
||||
subscription = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let subscription = self.subscription, !finished else { return }
|
||||
subscription.cancel()
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
}
|
||||
|
||||
var description: String { return "${instantiation}" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("finished", finished),
|
||||
("upstreamSubscription", subscription as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
% end
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Publishers.Filter.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/3/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes all elements that match a provided closure.
|
||||
///
|
||||
/// - 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.
|
||||
public func filter(
|
||||
_ isIncluded: @escaping (Output) -> Bool
|
||||
) -> Publishers.Filter<Self> {
|
||||
return Publishers.Filter(upstream: self, isIncluded: isIncluded)
|
||||
}
|
||||
|
||||
/// Republishes all elements that match a provided 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.
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Self> {
|
||||
return Publishers.TryFilter(upstream: self, isIncluded: isIncluded)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Filter {
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: @escaping (Output) -> Bool
|
||||
) -> Publishers.Filter<Upstream> {
|
||||
return .init(upstream: upstream) { self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Upstream> {
|
||||
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryFilter {
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: @escaping (Output) -> Bool
|
||||
) -> Publishers.TryFilter<Upstream> {
|
||||
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Upstream> {
|
||||
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes all elements that match a provided closure.
|
||||
public struct Filter<Upstream: Publisher>: 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
|
||||
|
||||
/// A closure that indicates whether to republish an element.
|
||||
public let isIncluded: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, isIncluded: @escaping (Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.isIncluded = isIncluded
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes all elements that match
|
||||
/// a provided error-throwing closure.
|
||||
public struct TryFilter<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 = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A error-throwing closure that indicates whether to republish an element.
|
||||
public let isIncluded: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
isIncluded: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.isIncluded = isIncluded
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Filter {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
|
||||
return filter(newValue) ? .continue(newValue) : .continue(nil)
|
||||
}
|
||||
|
||||
override var description: String { return "Filter" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryFilter {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(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> {
|
||||
do {
|
||||
return try filter(newValue) ? .continue(newValue) : .continue(nil)
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryFilter" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// Publishers.First.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/8/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the first element of a stream, then finishes.
|
||||
///
|
||||
/// If this publisher doesn’t receive any elements, it finishes without publishing.
|
||||
/// - 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.
|
||||
///
|
||||
/// 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 satifies 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.
|
||||
///
|
||||
/// 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 satifies the predicate.
|
||||
public func tryFirst(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFirstWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the first element of a stream, then finishes.
|
||||
public struct First<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
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the first element of a
|
||||
/// stream to satisfy a predicate closure.
|
||||
public struct FirstWhere<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 closure that determines whether to publish an element.
|
||||
public let predicate: (Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the first element of a stream
|
||||
/// to satisfy a throwing predicate closure.
|
||||
public struct TryFirstWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether to publish an element.
|
||||
public let predicate: (Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.First: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.First {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
Void>
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result = newValue
|
||||
return .finished
|
||||
}
|
||||
|
||||
override var description: String { return "First" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FirstWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream, Output, Output, Failure, (Output) -> Bool>
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream, predicate: @escaping (Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if reduce(newValue) {
|
||||
result = newValue
|
||||
return .finished
|
||||
} else {
|
||||
return .continue
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryFirst" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryFirstWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Output) throws -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Output
|
||||
) -> PartialCompletion<Void, Error> {
|
||||
do {
|
||||
if try reduce(newValue) {
|
||||
result = newValue
|
||||
return .finished
|
||||
} else {
|
||||
return .continue
|
||||
}
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryFirstWhere" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
//
|
||||
// Publishers.FlatMap.swift
|
||||
//
|
||||
// Created by Eric Patey on 16.08.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
/// Transforms all elements from an upstream publisher into a new or existing
|
||||
/// publisher.
|
||||
///
|
||||
/// `flatMap` merges the output from all returned publishers into a single stream of
|
||||
/// output.
|
||||
///
|
||||
/// - 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.
|
||||
/// - Returns: A publisher that transforms elements from an upstream publisher into
|
||||
/// a publisher of that element’s type.
|
||||
public func flatMap<Result, Child: Publisher>(
|
||||
maxPublishers: Subscribers.Demand = .unlimited,
|
||||
_ transform: @escaping (Output) -> Child
|
||||
) -> Publishers.FlatMap<Child, Self>
|
||||
where Result == Child.Output, Failure == Child.Failure {
|
||||
return .init(upstream: self,
|
||||
maxPublishers: maxPublishers,
|
||||
transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct FlatMap<Child: Publisher, Upstream: Publisher>: Publisher
|
||||
where Child.Failure == Upstream.Failure
|
||||
{
|
||||
public typealias Output = Child.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let maxPublishers: Subscribers.Demand
|
||||
|
||||
public let transform: (Upstream.Output) -> Child
|
||||
|
||||
public init(upstream: Upstream, maxPublishers: Subscribers.Demand,
|
||||
transform: @escaping (Upstream.Output) -> Child) {
|
||||
self.upstream = upstream
|
||||
self.maxPublishers = maxPublishers
|
||||
self.transform = transform
|
||||
}
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
maxPublishers: maxPublishers,
|
||||
map: transform)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Child.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private typealias SubscriptionIndex = Int
|
||||
|
||||
/// All requests to this subscription should be made with the `outerLock`
|
||||
/// acquired.
|
||||
private var outerSubscription: Subscription?
|
||||
|
||||
// Must be recursive lock. Probably a bug in Combine.
|
||||
/// The lock for requesting from `outerSubscription`.
|
||||
private let outerLock = UnfairLock.allocate()
|
||||
|
||||
/// The lock for modifying the state. All mutable state here should be
|
||||
/// read and modified with this lock acquired.
|
||||
/// The only exception is the `downstreamRecursive` field, which is guarded
|
||||
/// by the `downstreamLock`.
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
/// All the calls to the downstream subscriber should be made with this lock
|
||||
/// acquired.
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
/// This variable is set to `true` whenever we call `downstream.receive(_:)`,
|
||||
/// and then set back to `false`.
|
||||
private var downstreamRecursive = false
|
||||
|
||||
private var innerRecursive = false
|
||||
private var subscriptions = [SubscriptionIndex : Subscription]()
|
||||
private var nextInnerIndex: SubscriptionIndex = 0
|
||||
private var pendingSubscriptions = 0
|
||||
private var buffer = [(SubscriptionIndex, Child.Output)]()
|
||||
private let maxPublishers: Subscribers.Demand
|
||||
private let map: (Input) -> Child
|
||||
private var cancelledOrCompleted = false
|
||||
private var outerFinished = false
|
||||
|
||||
init(downstream: Downstream,
|
||||
maxPublishers: Subscribers.Demand,
|
||||
map: @escaping (Upstream.Output) -> Child) {
|
||||
self.downstream = downstream
|
||||
self.maxPublishers = maxPublishers
|
||||
self.map = map
|
||||
}
|
||||
|
||||
deinit {
|
||||
outerLock.deallocate()
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
// MARK: - Subscriber
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard outerSubscription == nil, !cancelledOrCompleted else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
outerSubscription = subscription
|
||||
lock.unlock()
|
||||
subscription.request(maxPublishers)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
let cancelledOrCompleted = self.cancelledOrCompleted
|
||||
lock.unlock()
|
||||
if cancelledOrCompleted {
|
||||
return .none
|
||||
}
|
||||
let child = map(input)
|
||||
lock.lock()
|
||||
let innerIndex = nextInnerIndex
|
||||
nextInnerIndex += 1
|
||||
pendingSubscriptions += 1
|
||||
lock.unlock()
|
||||
child.subscribe(Side(index: innerIndex, inner: self))
|
||||
return .none
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
|
||||
outerSubscription = nil
|
||||
lock.lock()
|
||||
outerFinished = true
|
||||
switch completion {
|
||||
case .finished:
|
||||
releaseLockThenSendCompletionDownstreamIfNeeded(outerFinished: true)
|
||||
return
|
||||
case .failure:
|
||||
let wasAlreadyCancelledOrCompleted = cancelledOrCompleted
|
||||
cancelledOrCompleted = true
|
||||
for (_, subscription) in subscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
subscriptions = [:]
|
||||
lock.unlock()
|
||||
if wasAlreadyCancelledOrCompleted {
|
||||
return
|
||||
}
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Subscription
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
if downstreamRecursive {
|
||||
// downstreamRecursive being true means that downstreamLock
|
||||
// is already acquired.
|
||||
downstreamDemand += demand
|
||||
return
|
||||
}
|
||||
lock.lock()
|
||||
if cancelledOrCompleted {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if demand == .unlimited {
|
||||
downstreamDemand = .unlimited
|
||||
let buffer = self.buffer
|
||||
self.buffer = []
|
||||
let subscriptions = self.subscriptions
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstreamRecursive = true
|
||||
for (_, childOutput) in buffer {
|
||||
_ = downstream.receive(childOutput)
|
||||
}
|
||||
downstreamRecursive = false
|
||||
downstreamLock.unlock()
|
||||
for (_, subscription) in subscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
lock.lock()
|
||||
} else {
|
||||
downstreamDemand += demand
|
||||
while !buffer.isEmpty && downstreamDemand > 0 {
|
||||
// FIXME: This has quadratic complexity.
|
||||
// This is what Combine does.
|
||||
// Can we improve perfomance by using e. g. Deque instead of Array?
|
||||
// Or array's cache locality makes this solution more efficient?
|
||||
// Must benchmark before optimizing!
|
||||
//
|
||||
// https://www.cocoawithlove.com/blog/2016/09/22/deque.html
|
||||
let (index, value) = buffer.removeFirst()
|
||||
downstreamDemand -= 1
|
||||
let subscription = subscriptions[index]
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstreamRecursive = true
|
||||
let additionalDemand = downstream.receive(value)
|
||||
downstreamRecursive = false
|
||||
downstreamLock.unlock()
|
||||
if additionalDemand != .none {
|
||||
lock.lock()
|
||||
downstreamDemand += additionalDemand
|
||||
lock.unlock()
|
||||
}
|
||||
if let subscription = subscription {
|
||||
innerRecursive = true
|
||||
subscription.request(.max(1))
|
||||
innerRecursive = false
|
||||
}
|
||||
lock.lock()
|
||||
}
|
||||
}
|
||||
releaseLockThenSendCompletionDownstreamIfNeeded(outerFinished: outerFinished)
|
||||
}
|
||||
|
||||
fileprivate func cancel() {
|
||||
lock.lock()
|
||||
cancelledOrCompleted = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = [:]
|
||||
lock.unlock()
|
||||
for (_, subscription) in subscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
// Combine doesn't acquire the lock here. Weird.
|
||||
outerSubscription?.cancel()
|
||||
outerSubscription = nil
|
||||
}
|
||||
|
||||
// MARK: - Reflection
|
||||
|
||||
fileprivate var description: String { return "FlatMap" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func receiveInner(subscription: Subscription,
|
||||
_ index: SubscriptionIndex) {
|
||||
lock.lock()
|
||||
pendingSubscriptions -= 1
|
||||
subscriptions[index] = subscription
|
||||
|
||||
let demand = downstreamDemand == .unlimited
|
||||
? Subscribers.Demand.unlimited
|
||||
: .max(1)
|
||||
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
private func receiveInner(_ input: Child.Output,
|
||||
_ index: SubscriptionIndex) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if downstreamDemand == .unlimited {
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstreamRecursive = true
|
||||
_ = downstream.receive(input)
|
||||
downstreamRecursive = false
|
||||
downstreamLock.unlock()
|
||||
return .none
|
||||
}
|
||||
if downstreamDemand == .none || innerRecursive {
|
||||
buffer.append((index, input))
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
downstreamDemand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstreamRecursive = true
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamRecursive = false
|
||||
downstreamLock.unlock()
|
||||
if newDemand > 0 {
|
||||
lock.lock()
|
||||
downstreamDemand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
private func receiveInner(completion: Subscribers.Completion<Child.Failure>,
|
||||
_ index: SubscriptionIndex) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
subscriptions.removeValue(forKey: index)
|
||||
let downstreamCompleted = releaseLockThenSendCompletionDownstreamIfNeeded(
|
||||
outerFinished: outerFinished
|
||||
)
|
||||
if !downstreamCompleted {
|
||||
requestOneMorePublisher()
|
||||
}
|
||||
case .failure:
|
||||
lock.lock()
|
||||
if cancelledOrCompleted {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelledOrCompleted = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = [:]
|
||||
lock.unlock()
|
||||
for (i, subscription) in subscriptions where i != index {
|
||||
subscription.cancel()
|
||||
}
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func requestOneMorePublisher() {
|
||||
if maxPublishers != .unlimited {
|
||||
outerLock.lock()
|
||||
outerSubscription?.request(.max(1))
|
||||
outerLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
/// - Precondition: `lock` is acquired
|
||||
/// - Postcondition: `lock` is released
|
||||
///
|
||||
/// - Returns: `true` if a completion was sent downstream
|
||||
@discardableResult
|
||||
private func releaseLockThenSendCompletionDownstreamIfNeeded(
|
||||
outerFinished: Bool
|
||||
) -> Bool {
|
||||
#if DEBUG
|
||||
lock.assertOwner() // Sanity check
|
||||
#endif
|
||||
if !cancelledOrCompleted && outerFinished && buffer.isEmpty &&
|
||||
subscriptions.count + pendingSubscriptions == 0 {
|
||||
cancelledOrCompleted = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: .finished)
|
||||
downstreamLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Side
|
||||
|
||||
private struct Side: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible {
|
||||
private let index: SubscriptionIndex
|
||||
private let inner: Inner
|
||||
fileprivate let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(index: SubscriptionIndex, inner: Inner) {
|
||||
self.index = index
|
||||
self.inner = inner
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
inner.receiveInner(subscription: subscription, index)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Child.Output) -> Subscribers.Demand {
|
||||
return inner.receiveInner(input, index)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
|
||||
inner.receiveInner(completion: completion, index)
|
||||
}
|
||||
|
||||
fileprivate var description: String { return "FlatMap" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(
|
||||
("parentSubscription", inner.combineIdentifier)
|
||||
)
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// Publishers.HandleEvents.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Performs the specified closures when publisher events occur.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveSubscription: A closure that executes when the publisher receives
|
||||
/// the subscription from the upstream publisher. Defaults to `nil`.
|
||||
/// - receiveOutput: A closure that executes when the publisher receives a value
|
||||
/// from the upstream publisher. Defaults to `nil`.
|
||||
/// - receiveCompletion: A closure that executes when the publisher receives
|
||||
/// the completion from the upstream publisher. Defaults to `nil`.
|
||||
/// - receiveCancel: A closure that executes when the downstream receiver cancels
|
||||
/// publishing. Defaults to `nil`.
|
||||
/// - receiveRequest: A closure that executes when the publisher receives a request
|
||||
/// for more elements. Defaults to `nil`.
|
||||
/// - Returns: A publisher that performs the specified closures when publisher events
|
||||
/// occur.
|
||||
public func handleEvents(
|
||||
receiveSubscription: ((Subscription) -> Void)? = nil,
|
||||
receiveOutput: ((Output) -> Void)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
|
||||
receiveCancel: (() -> Void)? = nil,
|
||||
receiveRequest: ((Subscribers.Demand) -> Void)? = nil
|
||||
) -> Publishers.HandleEvents<Self> {
|
||||
return .init(upstream: self,
|
||||
receiveSubscription: receiveSubscription,
|
||||
receiveOutput: receiveOutput,
|
||||
receiveCompletion: receiveCompletion,
|
||||
receiveCancel: receiveCancel,
|
||||
receiveRequest: receiveRequest)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that performs the specified closures when publisher events occur.
|
||||
public struct HandleEvents<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
|
||||
|
||||
/// A closure that executes when the publisher receives the subscription from
|
||||
/// the upstream publisher.
|
||||
public var receiveSubscription: ((Subscription) -> Void)?
|
||||
|
||||
/// A closure that executes when the publisher receives a value from the upstream
|
||||
/// publisher.
|
||||
public var receiveOutput: ((Upstream.Output) -> Void)?
|
||||
|
||||
/// A closure that executes when the publisher receives the completion from
|
||||
/// the upstream publisher.
|
||||
public var receiveCompletion:
|
||||
((Subscribers.Completion<Upstream.Failure>) -> Void)?
|
||||
|
||||
/// A closure that executes when the downstream receiver cancels publishing.
|
||||
public var receiveCancel: (() -> Void)?
|
||||
|
||||
/// A closure that executes when the publisher receives a request for more
|
||||
/// elements.
|
||||
public var receiveRequest: ((Subscribers.Demand) -> Void)?
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
receiveSubscription: ((Subscription) -> Void)? = nil,
|
||||
receiveOutput: ((Output) -> Void)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
|
||||
receiveCancel: (() -> Void)? = nil,
|
||||
receiveRequest: ((Subscribers.Demand) -> Void)?
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.receiveSubscription = receiveSubscription
|
||||
self.receiveOutput = receiveOutput
|
||||
self.receiveCompletion = receiveCompletion
|
||||
self.receiveCancel = receiveCancel
|
||||
self.receiveRequest = receiveRequest
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(self, downstream: subscriber)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.HandleEvents {
|
||||
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 var status = SubscriptionStatus.awaitingSubscription
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var events: Publishers.HandleEvents<Upstream>?
|
||||
private let downstream: Downstream
|
||||
|
||||
init(_ events: Publishers.HandleEvents<Upstream>, downstream: Downstream) {
|
||||
self.events = events
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
events?.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)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
events?.receiveOutput?(input)
|
||||
let newDemand = downstream.receive(input)
|
||||
if newDemand > 0 {
|
||||
events?.receiveRequest?(newDemand)
|
||||
}
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
events?.receiveCompletion?(completion)
|
||||
lock.lock()
|
||||
events = nil
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
events?.receiveRequest?(demand)
|
||||
lock.lock()
|
||||
if case let .subscribed(subscription) = status {
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
return
|
||||
}
|
||||
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.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "HandleEvents" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Publishers.IgnoreOutput.swift
|
||||
//
|
||||
// Created by Eric Patey on 16.08.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ingores all upstream elements, but passes along a completion
|
||||
/// state (finished or failed).
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
/// A publisher that ignores all upstream elements, but passes along a completion
|
||||
/// state (finish or failed).
|
||||
public struct IgnoreOutput<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Never
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Upstream.Failure, Downstream.Input == Never
|
||||
{
|
||||
upstream.subscribe(Inner<Downstream>(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.IgnoreOutput {
|
||||
private final class 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()
|
||||
|
||||
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)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return .none
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.IgnoreOutput: Equatable where Upstream: Equatable {}
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// Publishers.Last.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/9/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Only publishes the last element of a stream, after the stream finishes.
|
||||
/// - 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.
|
||||
///
|
||||
/// - 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.
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
public func tryLast(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryLastWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that only publishes the last element of a stream,
|
||||
/// after the stream finishes.
|
||||
public struct Last<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
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the last element of a stream that satisfies
|
||||
/// a predicate closure, once the stream finishes.
|
||||
public struct LastWhere<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 closure that determines whether to publish an element.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the last element of a stream that satisfies
|
||||
/// an error-throwing predicate closure, once the stream finishes.
|
||||
public struct TryLastWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether to publish an element.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Error, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Last: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Last {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
Void>
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result = newValue
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Last" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.LastWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if reduce(newValue) {
|
||||
result = newValue
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "LastWhere" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryLastWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if try reduce(newValue) {
|
||||
result = newValue
|
||||
}
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryLastWhere" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Publishers.MakeConnectable.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Creates a connectable wrapper around the publisher.
|
||||
///
|
||||
/// - Returns: A `ConnectablePublisher` wrapping this publisher.
|
||||
public func makeConnectable() -> Publishers.MakeConnectable<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct MakeConnectable<Upstream: Publisher>: ConnectablePublisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
private let inner: Multicast<Upstream, PassthroughSubject<Output, Failure>>
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
inner = upstream.multicast(subject: .init())
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
inner.subscribe(subscriber)
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
return inner.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ extension Publisher {
|
||||
/// - 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>(
|
||||
_ transform: @escaping (Self.Output) throws -> Result
|
||||
_ transform: @escaping (Output) throws -> Result
|
||||
) -> Publishers.TryMap<Self, Result> {
|
||||
return Publishers.TryMap(upstream: self, transform: transform)
|
||||
}
|
||||
@@ -39,7 +39,7 @@ extension Publisher {
|
||||
extension Publishers {
|
||||
/// A publisher that transforms all elements from the upstream publisher with
|
||||
/// a provided closure.
|
||||
public struct Map<Upstream: Publisher, Output> : Publisher {
|
||||
public struct Map<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
@@ -54,6 +54,12 @@ extension Publishers {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, map: transform))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that transforms all elements from the upstream publisher
|
||||
@@ -78,12 +84,6 @@ extension Publishers {
|
||||
}
|
||||
|
||||
extension Publishers.Map {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
|
||||
public func map<Result>(
|
||||
_ transform: @escaping (Output) -> Result
|
||||
@@ -103,8 +103,7 @@ extension Publishers.TryMap {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.subscribe(inner)
|
||||
upstream.subscribe(Inner(downstream: subscriber, map: transform))
|
||||
}
|
||||
|
||||
public func map<Result>(
|
||||
@@ -120,89 +119,158 @@ extension Publishers.TryMap {
|
||||
}
|
||||
}
|
||||
|
||||
private class _Map<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Transform = (Input) -> Result<Downstream.Input, Downstream.Failure>
|
||||
|
||||
fileprivate var _transform: Transform?
|
||||
|
||||
var isCompleted: Bool {
|
||||
return _transform == nil
|
||||
}
|
||||
|
||||
init(downstream: Downstream, transform: @escaping Transform) {
|
||||
_transform = transform
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
switch _transform?(input) {
|
||||
case .success(let output)?:
|
||||
return downstream.receive(output)
|
||||
case .failure(let error)?:
|
||||
downstream.receive(completion: .failure(error))
|
||||
_transform = nil
|
||||
return .none
|
||||
case nil:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Map {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Map<Upstream, Downstream>,
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Downstream.Failure == Upstream.Failure
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let map: (Input) -> Output
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream, map: @escaping (Input) -> Output) {
|
||||
self.downstream = downstream
|
||||
self.map = map
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(map(input))
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Map" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryMap {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Map<Upstream, Downstream>,
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Downstream.Failure == Error
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let map: (Input) throws -> Output
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream,
|
||||
map: @escaping (Input) throws -> Output) {
|
||||
self.downstream = downstream
|
||||
self.map = map
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
do {
|
||||
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
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
_transform = nil
|
||||
super.cancel()
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "TryMap" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,11 @@ extension Publishers {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure,
|
||||
Upstream.Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let mapErrorSubscriber = _MapError<Upstream, SubscriberType>(
|
||||
downstream: subscriber,
|
||||
transform: transform
|
||||
)
|
||||
upstream.subscribe(mapErrorSubscriber)
|
||||
upstream.subscribe(Inner(downstream: subscriber, map: transform))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,48 +53,57 @@ extension Publisher {
|
||||
/// - Returns: A publisher that replaces any upstream failure with a
|
||||
/// new error produced by the `transform` closure.
|
||||
public func mapError<NewFailure: Error>(
|
||||
_ transform: @escaping (Self.Failure) -> NewFailure
|
||||
_ transform: @escaping (Failure) -> NewFailure
|
||||
) -> Publishers.MapError<Self, NewFailure>
|
||||
{
|
||||
return Publishers.MapError(upstream: self, transform)
|
||||
}
|
||||
}
|
||||
|
||||
private final class _MapError<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Output = Downstream.Input
|
||||
extension Publishers.MapError {
|
||||
|
||||
private let _transform: (Upstream.Failure) -> Downstream.Failure
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
var description: String { return "MapError" }
|
||||
private let downstream: Downstream
|
||||
private let map: (Upstream.Failure) -> Downstream.Failure
|
||||
|
||||
init(downstream: Downstream,
|
||||
transform: @escaping (Upstream.Failure) -> Downstream.Failure) {
|
||||
self._transform = transform
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
var description: String { return "MapError" }
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(_transform(error)))
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
init(downstream: Downstream,
|
||||
map: @escaping (Upstream.Failure) -> Downstream.Failure) {
|
||||
self.downstream = downstream
|
||||
self.map = map
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(map(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
%{
|
||||
from gyb_opencombine_support import (
|
||||
suffix_variadic,
|
||||
list_with_suffix_variadic
|
||||
)
|
||||
|
||||
instantiations = [(1, '', ''),
|
||||
(2, 'two', 'second '),
|
||||
(3, 'three', 'third ')]
|
||||
|
||||
def key_path_var(index, arity):
|
||||
return suffix_variadic('keyPath', index, arity)
|
||||
|
||||
def make_publisher_name(arity):
|
||||
return suffix_variadic('MapKeyPath', arity, arity)
|
||||
|
||||
def make_output_types(arity):
|
||||
return list_with_suffix_variadic('Output', arity)
|
||||
}%
|
||||
extension Publisher {
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
% result_types = list_with_suffix_variadic('Result', arity)
|
||||
% cs_result_types = ', '.join(result_types)
|
||||
%
|
||||
% method_args = \
|
||||
% ['_ {}: KeyPath<Output, {}>'.format(key_path_var(i, arity), result_types[i]) \
|
||||
% for i in range(arity)]
|
||||
% method_args_joined = ',\n '.join(method_args)
|
||||
%
|
||||
% init_args = ['{}: {}'.format(key_path_var(i, arity), key_path_var(i, arity)) \
|
||||
% for i in range(arity)]
|
||||
% init_args_joined = ',\n '.join(init_args)
|
||||
%
|
||||
% 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.
|
||||
///
|
||||
/// - 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`
|
||||
% 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}
|
||||
) -> Publishers.${publisher_name}<Self, ${cs_result_types}> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
${init_args_joined}
|
||||
)
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
% for arity, cardinal, ordinal in instantiations:
|
||||
%
|
||||
% doc_comment_suffix = 'value of a key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
%
|
||||
% output_types = make_output_types(arity)
|
||||
% cs_output_types = ', '.join(output_types)
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
|
||||
/// A publisher that publishes the ${doc_comment_suffix}.
|
||||
public struct ${publisher_name}<Upstream: Publisher, ${cs_output_types}>: Publisher {
|
||||
|
||||
% if arity != 1:
|
||||
public typealias Output = (${cs_output_types})
|
||||
|
||||
% end
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
% for i in range(arity):
|
||||
% ordinal = instantiations[i][2]
|
||||
/// The key path of a ${ordinal}property to publish.
|
||||
public let ${key_path_var(i, arity)}: KeyPath<Upstream.Output, ${output_types[i]}>
|
||||
|
||||
% end
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
% end
|
||||
}
|
||||
% for arity, _, _ in instantiations:
|
||||
% output_types = make_output_types(arity)
|
||||
% cs_output_types = ', '.join(output_types)
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
|
||||
extension Publishers.${publisher_name} {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
% for i in range(arity):
|
||||
private let ${key_path_var(i, arity)}: KeyPath<Input, ${output_types[i]}>
|
||||
|
||||
% end
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.${publisher_name}<Upstream, ${cs_output_types}>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
% for i in range(arity):
|
||||
self.${key_path_var(i, arity)} = parent.${key_path_var(i, arity)}
|
||||
% end
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
% output_components = \
|
||||
% ['input[keyPath: {}]'.format(key_path_var(i, arity)) for i in range(arity)]
|
||||
% output_components_joined = ',\n '.join(output_components)
|
||||
let output = (
|
||||
${output_components_joined}
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
% inner_description = 'ValueForKey' + ('' if arity == 1 else 's')
|
||||
var description: String { return "${inner_description}" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
% for i in range(arity):
|
||||
("${key_path_var(i, arity)}", ${key_path_var(i, arity)}),
|
||||
% end
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
% end
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Publishers.MeasureInterval.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
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
|
||||
/// scheduler.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scheduler: The scheduler on which to deliver elements.
|
||||
/// - options: Options that customize the delivery of elements.
|
||||
/// - Returns: A publisher that emits elements representing the time interval between
|
||||
/// the elements it receives.
|
||||
public func measureInterval<Context: Scheduler>(
|
||||
using scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil
|
||||
) -> Publishers.MeasureInterval<Self, Context> {
|
||||
return .init(upstream: self, scheduler: scheduler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that measures and emits the time interval between events received from
|
||||
/// an upstream publisher.
|
||||
public struct MeasureInterval<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Context.SchedulerTimeType.Stride
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The scheduler on which to deliver elements.
|
||||
public let scheduler: Context
|
||||
|
||||
public init(upstream: Upstream, scheduler: Context) {
|
||||
self.upstream = upstream
|
||||
self.scheduler = scheduler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Downstream.Input == Context.SchedulerTimeType.Stride
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MeasureInterval {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
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 var last: Context.SchedulerTimeType?
|
||||
|
||||
init(_ measureInterval: MeasureInterval, downstream: Downstream) {
|
||||
state = .ready(measureInterval, downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(measureInterval, downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(measureInterval, downstream, subscription)
|
||||
last = measureInterval.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 {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let now = measureInterval.scheduler.now
|
||||
last = now
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(previousTime.distance(to: now))
|
||||
if newDemand > 0 {
|
||||
subscription.request(newDemand)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
last = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
last = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "MeasureInterval" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.Merge.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 04/10/2019.
|
||||
//
|
||||
%{
|
||||
from gyb_opencombine_support import (
|
||||
suffix_variadic,
|
||||
list_with_suffix_variadic,
|
||||
indent
|
||||
)
|
||||
|
||||
import string
|
||||
|
||||
instantiations = [(2, 'two', 'A second'),
|
||||
(3, 'three', 'A third'),
|
||||
(4, 'four', 'A fourth'),
|
||||
(5, 'five', 'A fifth'),
|
||||
(6, 'six', 'A sixth'),
|
||||
(7, 'seven', 'A seventh'),
|
||||
(8, 'eight', 'An eighth')]
|
||||
|
||||
def make_publisher_name(arity):
|
||||
return suffix_variadic('Merge', arity, arity - 1)
|
||||
|
||||
def make_upstream_types(arity, start=0):
|
||||
return [str(c) for c in string.ascii_uppercase[start:arity]]
|
||||
|
||||
def make_upstream_types_reversed(arity):
|
||||
return [str(c) for c in reversed(string.ascii_uppercase)][:arity]
|
||||
|
||||
def make_upstream_generic_constraints(upstream_types, first_is_self=False):
|
||||
|
||||
format_string = '{0}Failure == {1}.Failure, {0}Output == {1}.Output'
|
||||
|
||||
def format(i):
|
||||
return format_string.format(upstream_types[i] + '.',
|
||||
upstream_types[i + 1])
|
||||
|
||||
result = [format(i) for i in range(len(upstream_types) - 1)]
|
||||
|
||||
if first_is_self:
|
||||
result.insert(0, format_string.format('', upstream_types[0]))
|
||||
|
||||
return result
|
||||
|
||||
def declare_merge_method(arg_count, arity, indent_spaces_count):
|
||||
assert(arg_count <= arity - 1)
|
||||
is_specialization = arg_count < arity - 1
|
||||
|
||||
declaration_format = """\
|
||||
public func merge<
|
||||
{}
|
||||
>(with {}) -> Publishers.{}<{}>\
|
||||
"""
|
||||
|
||||
where_clause_format = '\n where {}'
|
||||
|
||||
if arg_count == 1:
|
||||
upstream_types = ['P']
|
||||
elif is_specialization:
|
||||
upstream_types = make_upstream_types_reversed(arg_count)
|
||||
else:
|
||||
upstream_types = make_upstream_types(arg_count + 1, 1)
|
||||
|
||||
method_generic_params = \
|
||||
[upstream_type + ': Publisher' for upstream_type in upstream_types]
|
||||
|
||||
cs_method_generic_params = \
|
||||
(',\n ').join(method_generic_params)
|
||||
|
||||
method_args = ['other: P'] \
|
||||
if arg_count == 1 else ['{}: {}'.format(upstream_type.lower(), upstream_type) \
|
||||
for upstream_type in upstream_types]
|
||||
|
||||
cs_method_args = ',\n _ '.join(method_args)
|
||||
|
||||
publisher_name = make_publisher_name(arity)
|
||||
|
||||
self_generic_params = make_upstream_types(arity - arg_count) \
|
||||
if is_specialization else ['Self']
|
||||
|
||||
publisher_generic_params = self_generic_params + upstream_types
|
||||
|
||||
cs_publisher_generic_params = ', '.join(publisher_generic_params)
|
||||
|
||||
generic_constraints = make_upstream_generic_constraints(upstream_types, True)
|
||||
|
||||
cs_generic_constraints = \
|
||||
',\n '.join(generic_constraints)
|
||||
|
||||
declaration = declaration_format.format(cs_method_generic_params,
|
||||
cs_method_args,
|
||||
publisher_name,
|
||||
cs_publisher_generic_params)
|
||||
|
||||
if not is_specialization:
|
||||
declaration += where_clause_format.format(cs_generic_constraints)
|
||||
|
||||
return indent(declaration, indent_spaces_count)
|
||||
}%
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
// swiftlint:disable vertical_parameter_alignment
|
||||
|
||||
// MARK: - Merge methods on Publisher
|
||||
|
||||
extension Publisher {
|
||||
|
||||
% for arity, _, _ in instantiations:
|
||||
%
|
||||
% doc_cardinal = 'another publisher' \
|
||||
% if arity == 2 else (instantiations[arity - 2][1] + ' other publishers')
|
||||
% argument_names = ['other'] \
|
||||
% if arity == 2 else [upstream_type.lower() \
|
||||
% for upstream_type in make_upstream_types(arity, 1)]
|
||||
/// Combines elements from this publisher with those from ${doc_cardinal},
|
||||
/// delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// The merged publisher continues to emit elements until all upstream publishers
|
||||
/// finish. If an upstream publisher produces an error, the merged publisher fails
|
||||
/// with that error.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity - 1):
|
||||
% param_doc = 'Another' if arity == 2 else instantiations[i][2]
|
||||
/// - ${argument_names[i]}: ${param_doc} publisher.
|
||||
% end
|
||||
/// - Returns: A publisher that emits an event when any upstream publisher emits
|
||||
/// an event.
|
||||
${declare_merge_method(arity - 1, arity, 4)}
|
||||
{
|
||||
return .init(self, ${', '.join(argument_names)})
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combines elements from this publisher with those from another publisher of
|
||||
/// the same type, delivering an interleaved sequence of elements.
|
||||
///
|
||||
/// - Parameter other: Another publisher of this publisher's type.
|
||||
/// - Returns: A publisher that emits an event when either upstream publisher emits
|
||||
/// an event.
|
||||
public func merge(with other: Self) -> Publishers.MergeMany<Self> {
|
||||
return .init([self, other])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Merge publishers
|
||||
|
||||
extension Publishers {
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% upstream_types = make_upstream_types(arity)
|
||||
%
|
||||
% upstream_generic_params = \
|
||||
% [upstream_type + ': Publisher' for upstream_type in upstream_types]
|
||||
%
|
||||
% cs_upstream_generic_params = \
|
||||
% (',\n' + (19 + len(publisher_name)) * ' ').join(upstream_generic_params)
|
||||
%
|
||||
% upstream_generic_constraints = \
|
||||
% make_upstream_generic_constraints(upstream_types)
|
||||
%
|
||||
% cs_upstream_generic_constraints = \
|
||||
% ',\n '.join(upstream_generic_constraints)
|
||||
%
|
||||
% init_args = ['_ {}: {}'.format(upstream_type.lower(), upstream_type) \
|
||||
% for upstream_type in upstream_types]
|
||||
% cs_init_args = ',\n '.join(init_args)
|
||||
%
|
||||
% self_fields = [upstream_type.lower() for upstream_type in upstream_types]
|
||||
|
||||
/// A publisher created by applying the merge function to ${cardinal} upstream
|
||||
/// publishers.
|
||||
public struct ${publisher_name}<${cs_upstream_generic_params}>: Publisher
|
||||
where ${cs_upstream_generic_constraints}
|
||||
{
|
||||
public typealias Output = ${upstream_types[0]}.Output
|
||||
|
||||
public typealias Failure = ${upstream_types[0]}.Failure
|
||||
% for upstream_type in upstream_types:
|
||||
|
||||
public let ${upstream_type.lower()}: ${upstream_type}
|
||||
% end
|
||||
|
||||
public init(
|
||||
${cs_init_args}
|
||||
) {
|
||||
% for self_field in self_fields:
|
||||
self.${self_field} = ${self_field}
|
||||
% end
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where ${upstream_types[0]}.Failure == Downstream.Failure,
|
||||
${upstream_types[0]}.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: ${arity})
|
||||
% for i in range(len(self_fields)):
|
||||
${self_fields[i]}.subscribe(Merged.Side(index: ${i}, merger: merged))
|
||||
% end
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
% for i in range(len(instantiations) + 1 - arity):
|
||||
% argument_names = ['other'] \
|
||||
% if i == 0 else [upstream_type.lower() \
|
||||
% for upstream_type in make_upstream_types_reversed(i + 1)]
|
||||
%
|
||||
|
||||
${declare_merge_method(i + 1, arity + i + 1, 8)}
|
||||
{
|
||||
return .init(${', '.join(self_fields + argument_names)})
|
||||
}
|
||||
% end
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct MergeMany<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let publishers: [Upstream]
|
||||
|
||||
public init(_ upstream: Upstream...) {
|
||||
self.publishers = upstream
|
||||
}
|
||||
|
||||
public init<UpstreamPublishers: Swift.Sequence>(_ upstream: UpstreamPublishers)
|
||||
where Upstream == UpstreamPublishers.Element
|
||||
{
|
||||
publishers = Array(upstream)
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Merged = _Merged<Output, Failure, Downstream>
|
||||
let merged = Merged(downstream: subscriber, count: publishers.count)
|
||||
for (i, upstream) in publishers.enumerated() {
|
||||
upstream.subscribe(Merged.Side(index: i, merger: merged))
|
||||
}
|
||||
subscriber.receive(subscription: merged)
|
||||
}
|
||||
|
||||
public func merge(with other: Upstream) -> Publishers.MergeMany<Upstream> {
|
||||
var newPublishers = publishers
|
||||
newPublishers.append(other)
|
||||
return .init(newPublishers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable conformances
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% upstream_types = make_upstream_types(arity)
|
||||
%
|
||||
% constraints = [upstream_type + ': Equatable' for upstream_type in upstream_types]
|
||||
% cs_constraints = ',\n'.join(constraints)
|
||||
% cs_constraints = indent(cs_constraints, 8)
|
||||
%
|
||||
|
||||
extension Publishers.${publisher_name}: Equatable
|
||||
where
|
||||
${cs_constraints} {}
|
||||
% end
|
||||
|
||||
extension Publishers.MergeMany: Equatable
|
||||
where
|
||||
Upstream: Equatable {}
|
||||
@@ -7,6 +7,15 @@
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Applies a closure to create a subject that delivers elements to subscribers.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// - 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
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
@@ -15,6 +24,14 @@ extension Publisher {
|
||||
return Publishers.Multicast(upstream: self, createSubject: createSubject)
|
||||
}
|
||||
|
||||
/// Provides 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.
|
||||
/// In contrast with `multicast(_:)`, this method produces a publisher shares
|
||||
/// the provided Subject among all the downstream subscribers.
|
||||
///
|
||||
/// - Parameter subject: A subject to deliver elements to downstream subscribers.
|
||||
public func multicast<SubjectType: Subject>(
|
||||
subject: SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
@@ -26,43 +43,67 @@ extension Publisher {
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that uses a subject to deliver elements to multiple subscribers.
|
||||
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
|
||||
: ConnectablePublisher
|
||||
where Upstream.Failure == SubjectType.Failure,
|
||||
Upstream.Output == SubjectType.Output
|
||||
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.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure to create a new Subject each time a subscriber attaches
|
||||
/// to the multicast publisher.
|
||||
public let createSubject: () -> SubjectType
|
||||
|
||||
private lazy var _subject: SubjectType = self.createSubject()
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var subject: SubjectType?
|
||||
|
||||
private var lazySubject: SubjectType {
|
||||
lock.lock()
|
||||
if let subject = subject {
|
||||
lock.unlock()
|
||||
return subject
|
||||
}
|
||||
|
||||
let subject = createSubject()
|
||||
self.subject = subject
|
||||
lock.unlock()
|
||||
return subject
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// a subscriber attaches to the multicast publisher.
|
||||
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
|
||||
self.upstream = upstream
|
||||
self.createSubject = createSubject
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where SubjectType.Failure == Downstream.Failure,
|
||||
SubjectType.Output == Downstream.Input
|
||||
{
|
||||
_subject.subscribe(Inner(downstream: subscriber))
|
||||
lazySubject.subscribe(Inner(parent: self, downstream: subscriber))
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
|
||||
let subscriber = SubjectSubscriber(_subject)
|
||||
|
||||
upstream.subscribe(subscriber)
|
||||
|
||||
return AnyCancellable {
|
||||
subscriber.downstreamSubject = nil
|
||||
}
|
||||
return upstream.subscribe(lazySubject)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,32 +111,106 @@ extension Publishers {
|
||||
extension Publishers.Multicast {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
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
|
||||
|
||||
var description: String { return "Multicast" }
|
||||
private enum State {
|
||||
case ready(upstream: Upstream, downstream: Downstream)
|
||||
case subscribed(upstream: Upstream,
|
||||
downstream: Downstream,
|
||||
subjectSubscription: Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state: State
|
||||
|
||||
fileprivate init(parent: Publishers.Multicast<Upstream, SubjectType>,
|
||||
downstream: Downstream) {
|
||||
state = .ready(upstream: parent.upstream, downstream: downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
fileprivate var description: String { return "Multicast" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
lock.lock()
|
||||
guard case let .ready(upstream, downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(upstream: upstream,
|
||||
downstream: downstream,
|
||||
subjectSubscription: subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, subjectSubscription) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
if newDemand > 0 {
|
||||
subjectSubscription.request(newDemand)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subjectSubscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subjectSubscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subjectSubscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subjectSubscription.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// Publishers.Output.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements up to the specified maximum count.
|
||||
///
|
||||
/// - Parameter maxLength: The maximum number of elements to republish.
|
||||
/// - Returns: A publisher that publishes up to the specified number of elements
|
||||
/// before completing.
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Output<Self> {
|
||||
return output(in: ..<maxLength)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// - Parameter index: The index that indicates the element to publish.
|
||||
/// - Returns: A publisher that publishes a specific indexed element.
|
||||
public func output(at index: Int) -> Publishers.Output<Self> {
|
||||
return output(in: index...index)
|
||||
}
|
||||
|
||||
/// Publishes elements specified by their range in the sequence of published elements.
|
||||
///
|
||||
/// After all elements are published, 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.
|
||||
///
|
||||
/// - 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>
|
||||
where Range.Bound == Int
|
||||
{
|
||||
return .init(upstream: self, range: range.relative(to: 0 ..< .max))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes elements specified by a range in the sequence of
|
||||
/// published elements.
|
||||
public struct Output<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The range of elements to publish.
|
||||
public let range: CountableRange<Int>
|
||||
|
||||
/// Creates a publisher that publishes elements specified by a range.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - range: The range of elements to publish.
|
||||
public init(upstream: Upstream, range: CountableRange<Int>) {
|
||||
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
|
||||
precondition(range.upperBound >= 0, "upperBound must not be negative")
|
||||
self.upstream = upstream
|
||||
self.range = range
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, range: range))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Output: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Output {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
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
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var remainingUntilStart: Int
|
||||
|
||||
private var remainingCount: Int
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, range: CountableRange<Int>) {
|
||||
self.downstream = downstream
|
||||
self.remainingUntilStart = range.lowerBound
|
||||
self.remainingCount = range.count
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
if remainingUntilStart > 0 {
|
||||
remainingUntilStart -= 1
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
let newDemand: Subscribers.Demand
|
||||
if remainingCount > 0 {
|
||||
remainingCount -= 1
|
||||
newDemand = downstream.receive(input)
|
||||
} else {
|
||||
newDemand = .none
|
||||
cancelUpstreamAndFinish()
|
||||
}
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Output" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func cancelUpstreamAndFinish() {
|
||||
assert(remainingCount == 0)
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// Publishers.PrefixWhile.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements while a predicate closure indicates publishing should
|
||||
/// continue.
|
||||
///
|
||||
/// The publisher finishes when the closure returns `false`.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether publishing should continue.
|
||||
/// - Returns: A publisher that passes through elements until the predicate indicates
|
||||
/// publishing should finish.
|
||||
public func prefix(
|
||||
while predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.PrefixWhile<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Republishes elements while a 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.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether publishing should continue.
|
||||
/// - Returns: A publisher that passes through elements until the predicate throws or
|
||||
/// indicates publishing should finish.
|
||||
public func tryPrefix(
|
||||
while predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryPrefixWhile<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes elements while a predicate closure indicates
|
||||
/// publishing should continue.
|
||||
public struct PrefixWhile<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 closure that determines whether whether publishing should continue.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes elements while an error-throwing predicate closure
|
||||
/// indicates publishing should continue.
|
||||
public struct TryPrefixWhile<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether publishing should continue.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.PrefixWhile {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
override func receive(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
|
||||
return filter(newValue) ? .continue(newValue) : .finished
|
||||
}
|
||||
|
||||
override var description: String { return "PrefixWhile" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryPrefixWhile {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
override func receive(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
|
||||
do {
|
||||
return try filter(newValue) ? .continue(newValue) : .finished
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryPrefixWhile" }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,19 @@
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
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.
|
||||
/// - Returns: A publisher that prints log messages for all publishing events.
|
||||
public func print(_ prefix: String = "",
|
||||
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
|
||||
return .init(upstream: self, prefix: prefix, to: stream)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that prints log messages for all publishing events, optionally
|
||||
@@ -43,8 +56,8 @@ extension Publishers {
|
||||
self.stream = stream
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, prefix: prefix, stream: stream)
|
||||
upstream.subscribe(inner)
|
||||
@@ -52,112 +65,122 @@ extension Publishers {
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
extension Publishers.Print {
|
||||
private final class Inner<Downstream: Subscriber>: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Downstream.Input
|
||||
typealias Failure = Downstream.Failure
|
||||
|
||||
/// Prints log messages for all publishing events.
|
||||
///
|
||||
/// - Parameter prefix: A string with which to prefix all log messages. Defaults to
|
||||
/// an empty string.
|
||||
/// - Returns: A publisher that prints log messages for all publishing events.
|
||||
public func print(_ prefix: String = "",
|
||||
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
|
||||
return Publishers.Print(upstream: self, prefix: prefix, to: stream)
|
||||
}
|
||||
}
|
||||
/// A concrete type wrapper around an abstract stream.
|
||||
private struct PrintTarget: TextOutputStream {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
typealias Input = Downstream.Input
|
||||
typealias Failure = Downstream.Failure
|
||||
var stream: TextOutputStream
|
||||
|
||||
private var _downstream: Downstream
|
||||
private let _prefix: String
|
||||
private var _stream: TextOutputStream
|
||||
private var _upstreamSubscription: Subscription?
|
||||
private let _printerLock = Lock(recursive: false)
|
||||
|
||||
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
|
||||
_downstream = downstream
|
||||
_prefix = prefix
|
||||
_stream = stream ?? StdoutStream()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
_log("receive subscription", value: subscription)
|
||||
_upstreamSubscription = subscription
|
||||
_downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
_log("receive value", value: input)
|
||||
let demand = _downstream.receive(input)
|
||||
_logDemand(demand, synchronous: true)
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
_log("receive finished")
|
||||
case .failure(let error):
|
||||
_log("receive error", value: error)
|
||||
mutating func write(_ string: String) {
|
||||
stream.write(string)
|
||||
}
|
||||
}
|
||||
_downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
_logDemand(demand, synchronous: false)
|
||||
_upstreamSubscription?.request(demand)
|
||||
}
|
||||
private var downstream: Downstream
|
||||
private let prefix: String
|
||||
private var stream: PrintTarget?
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
func cancel() {
|
||||
_log("receive cancel")
|
||||
_upstreamSubscription?.cancel()
|
||||
_upstreamSubscription = nil
|
||||
}
|
||||
|
||||
var description: String { return "Print" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
private func _log(_ description: String,
|
||||
value: Any? = nil,
|
||||
additionalInfo: String = "") {
|
||||
_printerLock.do {
|
||||
if !_prefix.isEmpty {
|
||||
_stream.write(_prefix)
|
||||
_stream.write(": ")
|
||||
}
|
||||
_stream.write(description)
|
||||
if let value = value {
|
||||
_stream.write(": (")
|
||||
_stream.write(String(describing: value))
|
||||
_stream.write(")")
|
||||
}
|
||||
if !additionalInfo.isEmpty {
|
||||
_stream.write(" (")
|
||||
_stream.write(additionalInfo)
|
||||
_stream.write(")")
|
||||
}
|
||||
_stream.write("\n")
|
||||
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
|
||||
self.downstream = downstream
|
||||
self.prefix = prefix.isEmpty ? "" : "\(prefix): "
|
||||
self.stream = stream.map(PrintTarget.init)
|
||||
}
|
||||
}
|
||||
|
||||
private func _logDemand(_ demand: Subscribers.Demand, synchronous: Bool) {
|
||||
let synchronouslyStr = synchronous ? "synchronous" : ""
|
||||
if let max = demand.max {
|
||||
_log("request max", value: max, additionalInfo: synchronouslyStr)
|
||||
} else {
|
||||
_log("request unlimited", additionalInfo: synchronouslyStr)
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
log("\(prefix)receive subscription: (\(subscription))")
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
log("\(prefix)receive value: (\(input))")
|
||||
let demand = downstream.receive(input)
|
||||
|
||||
if let max = demand.max {
|
||||
log("\(prefix)request max: (\(max)) (synchronous)")
|
||||
} else {
|
||||
log("\(prefix)request unlimited (synchronous)")
|
||||
}
|
||||
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
log("\(prefix)receive finished")
|
||||
case .failure(let error):
|
||||
log("\(prefix)receive error: (\(error))")
|
||||
}
|
||||
lock.lock()
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let max = demand.max {
|
||||
log("\(prefix)request max: (\(max))")
|
||||
} else {
|
||||
log("\(prefix)request unlimited")
|
||||
}
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
log("\(prefix)receive cancel")
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Print" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func log(_ text: String) {
|
||||
if var stream = stream {
|
||||
Swift.print(text, to: &stream)
|
||||
} else {
|
||||
Swift.print(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct StdoutStream: TextOutputStream {
|
||||
mutating func write(_ string: String) {
|
||||
print(string, terminator: "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// Publishers.ReceiveOn.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 02.12.2019.
|
||||
//
|
||||
|
||||
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,
|
||||
/// `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`.
|
||||
///
|
||||
/// // Some publisher.
|
||||
/// let jsonPublisher = MyJSONLoaderPublisher()
|
||||
///
|
||||
/// // Some subscriber that updates the UI.
|
||||
/// let labelUpdater = MyLabelUpdateSubscriber()
|
||||
///
|
||||
/// jsonPublisher
|
||||
/// .subscribe(on: backgroundQueue)
|
||||
/// .receiveOn(on: RunLoop.main)
|
||||
/// .subscribe(labelUpdater)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scheduler: The scheduler the publisher is to use for element delivery.
|
||||
/// - options: Scheduler options that customize the element delivery.
|
||||
/// - Returns: A publisher that delivers elements using the specified scheduler.
|
||||
public func receive<Context: Scheduler>(
|
||||
on scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil
|
||||
) -> Publishers.ReceiveOn<Self, Context> {
|
||||
return .init(upstream: self, scheduler: scheduler, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that delivers elements to its downstream subscriber on a specific
|
||||
/// scheduler.
|
||||
public struct ReceiveOn<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The scheduler the publisher is to use for element delivery.
|
||||
public let scheduler: Context
|
||||
|
||||
/// Scheduler options that customize the delivery of elements.
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.upstream = upstream
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.ReceiveOn {
|
||||
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
|
||||
|
||||
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 downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
init(_ receiveOn: ReceiveOn, downstream: Downstream) {
|
||||
state = .ready(receiveOn, downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(receiveOn, downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(receiveOn, downstream, subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(receiveOn, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
|
||||
self?.scheduledReceive(input, downstream: downstream)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
private func scheduledReceive(_ input: Upstream.Output, downstream: Downstream) {
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else {
|
||||
return
|
||||
}
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(newDemand)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(receiveOn, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
|
||||
self?.scheduledReceive(completion: completion, downstream: downstream)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>,
|
||||
downstream: Downstream) {
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "ReceiveOn" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// Publishers.Reduce.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Applies a closure that accumulates each element of a stream and publishes
|
||||
/// a final result upon completion.
|
||||
///
|
||||
/// - 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.
|
||||
/// - Returns: A publisher that applies the closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Reduce<Self, Accumulator> {
|
||||
return .init(upstream: self,
|
||||
initial: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
|
||||
/// Applies an error-throwing closure that accumulates 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.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The value the closure receives the first time it is 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>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.TryReduce<Self, Accumulator> {
|
||||
return .init(upstream: self,
|
||||
initial: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that applies a closure to all received elements and produces
|
||||
/// an accumulated value when the upstream publisher finishes.
|
||||
public struct Reduce<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The initial value provided on the first invocation of the closure.
|
||||
public let initial: Output
|
||||
|
||||
/// A closure that takes the previously-accumulated value and the next element
|
||||
/// from the upstream publisher to produce a new value.
|
||||
public let nextPartialResult: (Output, Upstream.Output) -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
initial: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) -> Output) {
|
||||
self.upstream = upstream
|
||||
self.initial = initial
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
initial: initial,
|
||||
reduce: nextPartialResult)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that applies an error-throwing closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
public struct TryReduce<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The initial value provided on the first invocation of the closure.
|
||||
public let initial: Output
|
||||
|
||||
/// An error-throwing closure that takes the previously-accumulated value and
|
||||
/// the next element from the upstream to produce a new value.
|
||||
///
|
||||
/// If this closure throws an error, the publisher fails and passes the error
|
||||
/// to its subscriber.
|
||||
public let nextPartialResult: (Output, Upstream.Output) throws -> Output
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
initial: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.initial = initial
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
initial: initial,
|
||||
reduce: nextPartialResult)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Reduce {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Output, Upstream.Output) -> Output>
|
||||
where Downstream.Input == Output, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result = reduce(result!, newValue)
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Reduce" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryReduce {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Output, Upstream.Output) throws -> Output>
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
result = try reduce(result!, newValue)
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryReduce" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// Publishers.RemoveDuplicates.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.10.2019.
|
||||
//
|
||||
|
||||
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.
|
||||
public func removeDuplicates() -> Publishers.RemoveDuplicates<Self> {
|
||||
return removeDuplicates(by: ==)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes only elements that don’t match the previous element, as evaluated by
|
||||
/// a provided closure.
|
||||
///
|
||||
/// - 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.
|
||||
public func removeDuplicates(
|
||||
by predicate: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.RemoveDuplicates<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes only elements that don’t match the previous element, as evaluated by
|
||||
/// a provided error-throwing closure.
|
||||
///
|
||||
/// - 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.
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryRemoveDuplicates<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes only elements that don’t match the previous element.
|
||||
public struct RemoveDuplicates<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
|
||||
|
||||
/// A closure to evaluate whether two elements are equivalent,
|
||||
/// for purposes of filtering.
|
||||
public let predicate: (Output, Output) -> Bool
|
||||
|
||||
/// Creates a publisher that publishes only elements that don’t match the previou
|
||||
/// element, as evaluated by a provided closure.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
/// - 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.
|
||||
public init(upstream: Upstream, predicate: @escaping (Output, Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// An error-throwing closure to evaluate whether two elements are equivalent,
|
||||
/// for purposes of filtering.
|
||||
public let predicate: (Output, Output) throws -> Bool
|
||||
|
||||
/// Creates a publisher that publishes only elements that don’t match the previous
|
||||
/// element, as evaluated by a provided error-throwing closure.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
/// - Parameter predicate: An error-throwing 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.
|
||||
public init(upstream: Upstream,
|
||||
predicate: @escaping (Output, Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.RemoveDuplicates {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(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(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
|
||||
let last = self.last
|
||||
self.last = newValue
|
||||
return last.map {
|
||||
filter($0, newValue) ? .continue(nil) : .continue(newValue)
|
||||
} ?? .continue(newValue)
|
||||
}
|
||||
|
||||
override var description: String { return "RemoveDuplicates" }
|
||||
|
||||
override var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("last", last as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryRemoveDuplicates {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(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(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
|
||||
let last = self.last
|
||||
self.last = newValue
|
||||
return last.map {
|
||||
do {
|
||||
return try filter($0, newValue)
|
||||
? .continue(nil)
|
||||
: .continue(newValue)
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
} ?? .continue(newValue)
|
||||
}
|
||||
|
||||
override var description: String { return "TryRemoveDuplicates" }
|
||||
|
||||
override var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("last", last as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// Publishers.ReplaceEmpty.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Joe Spadafora on 12/10/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces an empty stream with the provided element.
|
||||
///
|
||||
/// If the upstream publisher finishes without producing any elements,
|
||||
/// this publisher emits the provided element, then finishes normally.
|
||||
/// - Parameter output: An element to emit when the upstream publisher
|
||||
/// finishes without emitting any elements.
|
||||
/// - Returns: A publisher that replaces an empty stream with
|
||||
/// the provided output element.
|
||||
public func replaceEmpty(with output: Output) -> Publishers.ReplaceEmpty<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that replaces an empty stream with a provided element.
|
||||
public struct ReplaceEmpty<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The element to deliver when the upstream publisher finishes
|
||||
/// without delivering any elements.
|
||||
public let output: Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream, output: Output) {
|
||||
self.upstream = upstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, output: output)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.ReplaceEmpty: Equatable
|
||||
where Upstream: Equatable, Upstream.Output: Equatable {}
|
||||
|
||||
extension Publishers.ReplaceEmpty {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let output: Output
|
||||
private let downstream: Downstream
|
||||
|
||||
private var receivedUpstream = false
|
||||
private var lock = UnfairLock.allocate()
|
||||
private var downstreamRequested = false
|
||||
private var finishedWithoutUpstream = false
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
fileprivate init(downstream: Downstream, output: Output) {
|
||||
self.downstream = downstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
receivedUpstream = true
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
if receivedUpstream {
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
return
|
||||
}
|
||||
switch completion {
|
||||
case .finished:
|
||||
if downstreamRequested {
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: completion)
|
||||
return
|
||||
}
|
||||
finishedWithoutUpstream = true
|
||||
lock.unlock()
|
||||
case .failure:
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
downstreamRequested = true
|
||||
if finishedWithoutUpstream {
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
return
|
||||
}
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "ReplaceEmpty" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Publishers.ReplaceError.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Bogdan Vlad on 8/29/19.
|
||||
//
|
||||
|
||||
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.
|
||||
/// - 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.
|
||||
public func replaceError(with output: Output) -> Publishers.ReplaceError<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
/// A publisher that replaces any errors in the stream with a provided element.
|
||||
public struct ReplaceError<Upstream: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The element with which to replace errors from the upstream publisher.
|
||||
public let output: Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream,
|
||||
output: Output) {
|
||||
self.upstream = upstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, output: output)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.ReplaceError: Equatable
|
||||
where Upstream: Equatable, Upstream.Output: Equatable
|
||||
{}
|
||||
|
||||
extension Publishers.ReplaceError {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let output: Upstream.Output
|
||||
private let downstream: Downstream
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
private var terminated = false
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, output: Upstream.Output) {
|
||||
self.downstream = downstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
lock.unlock()
|
||||
if pendingDemand > 0 {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
pendingDemand -= 1
|
||||
lock.unlock()
|
||||
let demand = downstream.receive(input)
|
||||
guard demand > 0 else {
|
||||
return .none
|
||||
}
|
||||
lock.lock()
|
||||
pendingDemand += demand
|
||||
lock.unlock()
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
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 {
|
||||
terminated = true
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
if terminated {
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
return
|
||||
}
|
||||
pendingDemand += demand
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "ReplaceError" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// Publishers.Scan.swift
|
||||
//
|
||||
// Created by Eric Patey on 26.08.2019.
|
||||
//
|
||||
|
||||
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.
|
||||
///
|
||||
/// let pub = (0...5)
|
||||
/// .publisher
|
||||
/// .scan(0, { return $0 + $1 })
|
||||
/// .sink(receiveValue: { print ("\($0)", terminator: " ") })
|
||||
/// // Prints "0 1 3 6 10 15 ".
|
||||
///
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The previous result returned by the `nextPartialResult`
|
||||
/// closure.
|
||||
/// - nextPartialResult: A closure that takes as its arguments the previous value
|
||||
/// returned by the closure and the next element emitted from the upstream
|
||||
/// publisher.
|
||||
/// - Returns: A publisher that transforms elements by applying a closure that
|
||||
/// receives its previous return value and the next element from the upstream
|
||||
/// publisher.
|
||||
public func scan<Result>(
|
||||
_ initialResult: Result,
|
||||
_ nextPartialResult: @escaping (Result, Output) -> Result
|
||||
) -> Publishers.Scan<Self, Result> {
|
||||
return .init(upstream: self,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// If the closure throws an error, the publisher fails with the error.
|
||||
/// - Parameters:
|
||||
/// - initialResult: The previous result returned by the `nextPartialResult`
|
||||
/// closure.
|
||||
/// - nextPartialResult: An error-throwing closure that takes as its arguments the
|
||||
/// previous value returned by the closure and the next element emitted from the
|
||||
/// upstream publisher.
|
||||
/// - Returns: A publisher that transforms elements by applying a closure that
|
||||
/// receives its previous return value and the next element from the upstream
|
||||
/// publisher.
|
||||
public func tryScan<Result>(
|
||||
_ initialResult: Result,
|
||||
_ nextPartialResult: @escaping (Result, Output) throws -> Result
|
||||
) -> Publishers.TryScan<Self, Result> {
|
||||
return .init(upstream: self,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Scan<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let initialResult: Output
|
||||
|
||||
public let nextPartialResult: (Output, Upstream.Output) -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
initialResult: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) -> Output) {
|
||||
self.upstream = upstream
|
||||
self.initialResult = initialResult
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult))
|
||||
}
|
||||
}
|
||||
|
||||
public struct TryScan<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let initialResult: Output
|
||||
|
||||
public let nextPartialResult: (Output, Upstream.Output) throws -> Output
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
initialResult: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.initialResult = initialResult
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Scan {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let nextPartialResult: (Downstream.Input, Input) -> Downstream.Input
|
||||
|
||||
private var result: Downstream.Input
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
initialResult: Downstream.Input,
|
||||
nextPartialResult: @escaping (Downstream.Input, Input) -> Downstream.Input
|
||||
)
|
||||
{
|
||||
self.downstream = downstream
|
||||
self.result = initialResult
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
result = nextPartialResult(result, input)
|
||||
return downstream.receive(result)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Scan" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("result", result)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryScan {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let nextPartialResult:
|
||||
(Downstream.Input, Input) throws -> Downstream.Input
|
||||
|
||||
private var result: Downstream.Input
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
initialResult: Downstream.Input,
|
||||
nextPartialResult:
|
||||
@escaping (Downstream.Input, Input) throws -> Downstream.Input
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.nextPartialResult = nextPartialResult
|
||||
self.result = initialResult
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
do {
|
||||
result = try nextPartialResult(result, input)
|
||||
return downstream.receive(result)
|
||||
} catch {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
// Combine doesn't use locking in this method!
|
||||
guard case .subscribed = status else {
|
||||
return
|
||||
}
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "TryScan" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("status", status),
|
||||
("result", result)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,13 @@ extension Publishers {
|
||||
self.sequence = sequence
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure,
|
||||
Elements.Element == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure,
|
||||
Elements.Element == Downstream.Input
|
||||
{
|
||||
if let inner = Inner(downstream: subscriber, sequence: sequence) {
|
||||
var iterator = sequence.makeIterator()
|
||||
if iterator.next() != nil {
|
||||
let inner = Inner(downstream: subscriber, sequence: sequence)
|
||||
subscriber.receive(subscription: inner)
|
||||
} else {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
@@ -44,66 +46,93 @@ extension Publishers.Sequence {
|
||||
private final class Inner<Downstream: Subscriber, Elements: Sequence, Failure>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
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
|
||||
|
||||
private var _downstream: Downstream?
|
||||
private var _sequence: Elements?
|
||||
private var _iterator: Iterator?
|
||||
private var _nextValue: Element?
|
||||
private var sequence: Elements?
|
||||
private var downstream: Downstream?
|
||||
private var iterator: Iterator
|
||||
private var next: Element?
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private var recursion = false
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
init?(downstream: Downstream, sequence: Elements) {
|
||||
fileprivate init(downstream: Downstream, sequence: Elements) {
|
||||
self.sequence = sequence
|
||||
self.downstream = downstream
|
||||
self.iterator = sequence.makeIterator()
|
||||
next = iterator.next()
|
||||
}
|
||||
|
||||
// Early exit if the sequence is empty
|
||||
var iterator = sequence.makeIterator()
|
||||
guard iterator.next() != nil else { return nil }
|
||||
|
||||
_downstream = downstream
|
||||
_sequence = sequence
|
||||
_iterator = sequence.makeIterator()
|
||||
_nextValue = iterator.next()
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return _sequence.map(String.init(describing:)) ?? "Sequence"
|
||||
return sequence.map(String.init(describing:)) ?? "Sequence"
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: CollectionOfOne<(label: String?, value: Any)> =
|
||||
CollectionOfOne(("sequence", _sequence ?? [Element]()))
|
||||
let children =
|
||||
CollectionOfOne<Mirror.Child>(("sequence", sequence ?? [Element]()))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard downstream != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
pendingDemand += demand
|
||||
if recursion {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
guard let downstream = _downstream else { return }
|
||||
while let downstream = self.downstream, pendingDemand > 0 {
|
||||
if let current = self.next {
|
||||
pendingDemand -= 1
|
||||
|
||||
var demand = demand
|
||||
|
||||
while demand > 0 {
|
||||
if let nextValue = _nextValue {
|
||||
demand += downstream.receive(nextValue)
|
||||
demand -= 1
|
||||
// Combine calls next() while the lock is held.
|
||||
// It is possible to engineer a custom Sequence that would cause
|
||||
// a deadlock here, but it would be something insane.
|
||||
let next = iterator.next()
|
||||
recursion = true
|
||||
lock.unlock()
|
||||
let additionalDemand = downstream.receive(current)
|
||||
lock.lock()
|
||||
recursion = false
|
||||
pendingDemand += additionalDemand
|
||||
self.next = next
|
||||
}
|
||||
|
||||
_nextValue = _iterator?.next()
|
||||
|
||||
if _nextValue == nil {
|
||||
_downstream?.receive(completion: .finished)
|
||||
cancel()
|
||||
break
|
||||
if next == nil {
|
||||
self.downstream = nil
|
||||
self.sequence = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
_iterator = nil
|
||||
_sequence = nil
|
||||
lock.lock()
|
||||
downstream = nil
|
||||
sequence = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ extension Publishers {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
let inner = Inner(downstream: subscriber)
|
||||
upstream.subscribe(inner)
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
|
||||
public func setFailureType<NewFailure: Error>(
|
||||
@@ -63,12 +62,20 @@ extension Publisher where Failure == Never {
|
||||
}
|
||||
|
||||
extension Publishers.SetFailureType {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
private let downstream: Downstream
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
@@ -82,5 +89,11 @@ extension Publishers.SetFailureType {
|
||||
}
|
||||
|
||||
var description: String { return "SetFailureType" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Publishers.Share
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher as a class instance.
|
||||
///
|
||||
/// The downstream subscriber receieves elements and completion states unchanged from
|
||||
/// the upstream publisher. Use this operator when you want to use
|
||||
/// reference semantics, such as storing a publisher instance in a property.
|
||||
///
|
||||
/// - Returns: A class instance that republishes its upstream publisher.
|
||||
public func share() -> Publishers.Share<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher implemented as a class, which otherwise behaves like its upstream
|
||||
/// publisher.
|
||||
public final class Share<Upstream: Publisher>: Publisher, Equatable {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
private typealias MulticastSubject = PassthroughSubject<Output, Failure>
|
||||
|
||||
private let inner: Autoconnect<Multicast<Upstream, MulticastSubject>>
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.inner = upstream.multicast(subject: .init()).autoconnect()
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
inner.subscribe(subscriber)
|
||||
}
|
||||
|
||||
public static func == (lhs: Share, rhs: Share) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Publishers.SubscribeOn.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 02.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Specifies the scheduler on which to perform subscribe, cancel, and request
|
||||
/// 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`.
|
||||
///
|
||||
/// let ioPerformingPublisher == // Some publisher.
|
||||
/// let uiUpdatingSubscriber == // Some subscriber that updates the UI.
|
||||
///
|
||||
/// ioPerformingPublisher
|
||||
/// .subscribe(on: backgroundQueue)
|
||||
/// .receiveOn(on: RunLoop.main)
|
||||
/// .subscribe(uiUpdatingSubscriber)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scheduler: The scheduler on which to receive upstream messages.
|
||||
/// - options: Options that customize the delivery of elements.
|
||||
/// - Returns: A publisher which performs upstream operations on the specified
|
||||
/// scheduler.
|
||||
public func subscribe<Context: Scheduler>(
|
||||
on scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil
|
||||
) -> Publishers.SubscribeOn<Self, Context> {
|
||||
return .init(upstream: self, scheduler: scheduler, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that receives elements from an upstream publisher on a specific
|
||||
/// scheduler.
|
||||
public struct SubscribeOn<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The scheduler the publisher should use to receive elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// Scheduler options that customize the delivery of elements.
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.upstream = upstream
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
scheduler.schedule(options: options) {
|
||||
self.upstream.subscribe(Inner(self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SubscribeOn {
|
||||
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
|
||||
|
||||
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 upstreamLock = UnfairLock.allocate()
|
||||
|
||||
init(_ subscribeOn: SubscribeOn, downstream: Downstream) {
|
||||
state = .ready(subscribeOn, downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
upstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(subscribeOn, downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscribeOn, downstream, subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscribeOn, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscribeOn.scheduler.schedule(options: subscribeOn.options) { [weak self] in
|
||||
self?.scheduledRequest(demand, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledRequest(_ demand: Subscribers.Demand,
|
||||
subscription: Subscription) {
|
||||
upstreamLock.lock()
|
||||
subscription.request(demand)
|
||||
upstreamLock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscribeOn, _, subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscribeOn.scheduler.schedule(options: subscribeOn.options) { [weak self] in
|
||||
self?.scheduledCancel(subscription)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledCancel(_ subscription: Subscription) {
|
||||
upstreamLock.lock()
|
||||
subscription.cancel()
|
||||
upstreamLock.unlock()
|
||||
}
|
||||
|
||||
var description: String { return "SubscribeOn" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// Publishers._Merged.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publishers {
|
||||
// swiftlint:disable:next type_name
|
||||
internal final class _Merged<Input, Failure, Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Input, Downstream.Failure == Failure
|
||||
{
|
||||
private let downstream: Downstream
|
||||
private var demand = Subscribers.Demand.none // 0x78
|
||||
private var terminated = false // 0x80
|
||||
private let count: Int // 0x88
|
||||
private var upstreamFinished = 0 // 0x90
|
||||
private var finished = false // 0x98
|
||||
|
||||
// TODO: The size of these arrays always stays the same.
|
||||
// Maybe we can leverage ManagedBuffer/ManagedBufferPointer here
|
||||
// to avoid additional allocations.
|
||||
private var subscriptions: [Subscription?] // 0xA0
|
||||
private var buffers: [Input?] // 0xA8
|
||||
|
||||
private let lock = UnfairLock.allocate() // 0xB0
|
||||
private let downstreamLock = UnfairLock.allocate() // 0xB8
|
||||
private var recursive = false // 0xC0
|
||||
private var pending = Subscribers.Demand.none // 0xC8
|
||||
|
||||
internal init(downstream: Downstream, count: Int) {
|
||||
self.downstream = downstream
|
||||
self.count = count
|
||||
self.subscriptions = Array(repeating: nil, count: count)
|
||||
self.buffers = Array(repeating: nil, count: count)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
private func receive(subscription: Subscription, _ index: Int) {
|
||||
lock.lock()
|
||||
guard subscriptions[index] == nil else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
subscriptions[index] = subscription
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
subscription.request(demand == .unlimited ? .unlimited : .max(1))
|
||||
}
|
||||
|
||||
private func receive(_ input: Input, _ index: Int) -> Subscribers.Demand {
|
||||
func lockedSendValueDownstream() -> Subscribers.Demand {
|
||||
recursive = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
recursive = false
|
||||
return newDemand
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
if demand == .unlimited {
|
||||
let newDemand = lockedSendValueDownstream()
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
if demand == .none {
|
||||
buffers[index] = input
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
demand -= 1
|
||||
let newDemand = lockedSendValueDownstream()
|
||||
demand += newDemand + pending
|
||||
pending = .none
|
||||
lock.unlock()
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
private func receive(completion: Subscribers.Completion<Failure>, _ index: Int) {
|
||||
func lockedSendCompletionDownstream() {
|
||||
recursive = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
recursive = false
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
upstreamFinished += 1
|
||||
subscriptions[index] = nil
|
||||
// TODO: Test both conditions.
|
||||
// When receiving subscription twice, the second time
|
||||
// upstreamFinished != count
|
||||
guard upstreamFinished == count,
|
||||
subscriptions.allSatisfy({ $0 == nil }) else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
lockedSendCompletionDownstream()
|
||||
lock.unlock()
|
||||
case .failure:
|
||||
if terminated {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
terminated = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = Array(repeating: nil, count: subscriptions.count)
|
||||
lock.unlock()
|
||||
for (i, subscription) in subscriptions.enumerated() where i != index {
|
||||
subscription?.cancel()
|
||||
}
|
||||
lock.lock()
|
||||
lockedSendCompletionDownstream()
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
// TODO: Test all conditions
|
||||
if terminated || finished || demand == .none || self.demand == .unlimited {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if recursive {
|
||||
pending += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if demand == .unlimited {
|
||||
// loc_6a5b1
|
||||
self.demand = .unlimited
|
||||
}
|
||||
|
||||
// TODO: Unimplemented
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
// TODO: Unimplemented
|
||||
}
|
||||
|
||||
internal var description: String { return "Merge" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers._Merged {
|
||||
internal struct Side
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private let index: Int
|
||||
private let merger: Publishers._Merged<Input, Failure, Downstream>
|
||||
|
||||
internal let combineIdentifier = CombineIdentifier()
|
||||
|
||||
internal init(index: Int,
|
||||
merger: Publishers._Merged<Input, Failure, Downstream>) {
|
||||
self.index = index
|
||||
self.merger = merger
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
merger.receive(subscription: subscription, index)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return merger.receive(input, index)
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
merger.receive(completion: completion, index)
|
||||
}
|
||||
|
||||
internal var description: String { return "Merge" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
let children = CollectionOfOne<Mirror.Child>(
|
||||
("parentSubscription", merger.combineIdentifier)
|
||||
)
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// Record.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 12.11.2019.
|
||||
//
|
||||
|
||||
/// A publisher that allows for recording a series of inputs and a completion for later
|
||||
/// playback to each subscriber.
|
||||
public struct Record<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// The recorded output and completion.
|
||||
public let recording: Recording
|
||||
|
||||
/// Interactively record a series of outputs and a completion.
|
||||
public init(record: (inout Recording) -> Void) {
|
||||
var recording = Recording()
|
||||
record(&recording)
|
||||
self.init(recording: recording)
|
||||
}
|
||||
|
||||
/// Initialize with a recording.
|
||||
public init(recording: Recording) {
|
||||
self.recording = recording
|
||||
}
|
||||
|
||||
/// Set up a complete recording with the specified output and completion.
|
||||
public init(output: [Output], completion: Subscribers.Completion<Failure>) {
|
||||
self.init(recording: Recording(output: output, completion: completion))
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
if recording.output.isEmpty {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: recording.completion)
|
||||
} else {
|
||||
let inner = Inner(downstream: subscriber,
|
||||
sequence: recording.output,
|
||||
completion: recording.completion)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A recorded set of `Output` and a `Subscribers.Completion`.
|
||||
public struct Recording {
|
||||
|
||||
public typealias Input = Output
|
||||
|
||||
private enum State {
|
||||
case input
|
||||
case complete
|
||||
}
|
||||
|
||||
private var state: State
|
||||
|
||||
/// The output which will be sent to a `Subscriber`.
|
||||
public private(set) var output: [Output]
|
||||
|
||||
/// The completion which will be sent to a `Subscriber`.
|
||||
public private(set) var completion: Subscribers.Completion<Failure>
|
||||
|
||||
/// Set up a recording in a state ready to receive output.
|
||||
public init() {
|
||||
state = .input
|
||||
output = []
|
||||
completion = .finished
|
||||
}
|
||||
|
||||
/// Set up a complete recording with the specified output and completion.
|
||||
public init(output: [Output],
|
||||
completion: Subscribers.Completion<Failure> = .finished) {
|
||||
self.state = .complete
|
||||
self.output = output
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
/// Add an output to the recording.
|
||||
///
|
||||
/// A `fatalError` will be raised if output is added after adding completion.
|
||||
public mutating func receive(_ input: Input) {
|
||||
precondition(state == .input,
|
||||
"Receiving values after completion is not allowed")
|
||||
output.append(input)
|
||||
}
|
||||
|
||||
/// Add a completion to the recording.
|
||||
///
|
||||
/// A `fatalError` will be raised if more than one completion is added.
|
||||
public mutating func receive(completion: Subscribers.Completion<Failure>) {
|
||||
precondition(state == .input,
|
||||
"Receiving completion more than once is not allowed")
|
||||
self.completion = completion
|
||||
self.state = .complete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Record: Codable where Output: Codable, Failure: Codable {}
|
||||
|
||||
extension Record.Recording: Codable where Output: Codable, Failure: Codable {
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case output = "output"
|
||||
case completion = "completion"
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let output = try container.decode([Output].self, forKey: .output)
|
||||
let completion = try container.decode(Subscribers.Completion<Failure>.self,
|
||||
forKey: .completion)
|
||||
self.init(output: output, completion: completion)
|
||||
}
|
||||
|
||||
public func encode(into encoder: Encoder) throws {
|
||||
try encode(to: encoder)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(output, forKey: .output)
|
||||
try container.encode(completion, forKey: .completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension Record {
|
||||
|
||||
// This class is almost the same as Publishers.Sequence.Inner
|
||||
// despite some small details.
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
private var sequence: [Output]?
|
||||
private let completion: Subscribers.Completion<Failure>
|
||||
private var downstream: Downstream?
|
||||
private var iterator: IndexingIterator<[Output]>
|
||||
private var next: Output?
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private var recursion = false
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream,
|
||||
sequence: [Output],
|
||||
completion: Subscribers.Completion<Failure>) {
|
||||
self.sequence = sequence
|
||||
self.completion = completion
|
||||
self.downstream = downstream
|
||||
self.iterator = sequence.makeIterator()
|
||||
next = iterator.next()
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
var description: String {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return sequence.map { $0.description } ?? "Cancelled Events"
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("sequence", sequence ?? [Output]()),
|
||||
("completion", completion)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard downstream != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
pendingDemand += demand
|
||||
if recursion {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
while let downstream = self.downstream, pendingDemand > 0 {
|
||||
if let current = self.next {
|
||||
pendingDemand -= 1
|
||||
let next = iterator.next()
|
||||
recursion = true
|
||||
lock.unlock()
|
||||
let additionalDemand = downstream.receive(current)
|
||||
lock.lock()
|
||||
recursion = false
|
||||
pendingDemand += additionalDemand
|
||||
self.next = next
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
self.downstream = nil
|
||||
self.sequence = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
downstream = nil
|
||||
sequence = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,8 @@ extension Result {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubscriberType.Input == Success, SubscriberType.Failure == Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Success, Downstream.Failure == Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value):
|
||||
@@ -115,34 +115,44 @@ extension Result {
|
||||
#endif
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
extension Result.OCombine {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Success, Downstream.Failure == Failure
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
private var downstream: Downstream?
|
||||
private let output: Success
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
init(value: Success, downstream: Downstream) {
|
||||
self.output = value
|
||||
self.downstream = downstream
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
var description: String { return "Once" }
|
||||
func cancel() {
|
||||
downstream = nil
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
var description: String { return "Once" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,19 +44,3 @@ extension Result where Failure == Never {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An overload of `catching` that takes a non-thowing function and returns
|
||||
/// a function that returns an always succeeding `Result.`
|
||||
internal func catching<Input, Output, Failure: Error>(
|
||||
_ transform: @escaping (Input) -> Output
|
||||
) -> (Input) -> Result<Output, Failure> {
|
||||
return { input in .success(transform(input)) }
|
||||
}
|
||||
|
||||
/// Takes a function that may throw an error and returns a function that doesn't throw
|
||||
/// an error but returns `Result`.
|
||||
internal func catching<Input, Output>(
|
||||
_ transform: @escaping (Input) throws -> Output
|
||||
) -> (Input) -> Result<Output, Error> {
|
||||
return { input in Result { try transform(input) } }
|
||||
}
|
||||
|
||||
@@ -45,18 +45,3 @@ extension Subscriber where Input == Void {
|
||||
return receive(())
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: Subscriber {
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
self?.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Wrapped.Input) -> Subscribers.Demand {
|
||||
return self?.receive(input) ?? .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Wrapped.Failure>) {
|
||||
self?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,24 @@ extension Subscribers {
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
public private(set) var object: Root?
|
||||
|
||||
public let keyPath: ReferenceWritableKeyPath<Root, Input>
|
||||
|
||||
private var _upstreamSubscription: Subscription?
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
public var description: String { return "Assign \(Root.self)." }
|
||||
|
||||
public var customMirror: Mirror {
|
||||
let children: [(label: String?, value: Any)] = [
|
||||
(label: "object", value: object as Any),
|
||||
(label: "keyPath", value: keyPath),
|
||||
(label: "upstreamSubscription", value: _upstreamSubscription as Any)
|
||||
let children: [Mirror.Child] = [
|
||||
("object", object as Any),
|
||||
("keyPath", keyPath),
|
||||
("status", status as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
@@ -40,17 +43,21 @@ extension Subscribers {
|
||||
}
|
||||
|
||||
public func receive(subscription: Subscription) {
|
||||
if _upstreamSubscription == nil {
|
||||
_upstreamSubscription = subscription
|
||||
subscription.request(.unlimited)
|
||||
} else {
|
||||
switch status {
|
||||
case .subscribed, .terminal:
|
||||
subscription.cancel()
|
||||
case .awaitingSubscription:
|
||||
status = .subscribed(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
public func receive(_ value: Input) -> Subscribers.Demand {
|
||||
if _upstreamSubscription != nil {
|
||||
switch status {
|
||||
case .subscribed:
|
||||
object?[keyPath: keyPath] = value
|
||||
case .awaitingSubscription, .terminal:
|
||||
break
|
||||
}
|
||||
return .none
|
||||
}
|
||||
@@ -60,14 +67,17 @@ extension Subscribers {
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
_upstreamSubscription?.cancel()
|
||||
_upstreamSubscription = nil
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
return
|
||||
}
|
||||
subscription.cancel()
|
||||
status = .terminal
|
||||
object = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Self.Failure == Never {
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Assigns each element from a Publisher to a property on an object.
|
||||
///
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user