Compare commits
317 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8576f0d579 | |||
| 8f8ef5057d | |||
| ff31c43375 | |||
| 27d76e1fed | |||
| 5823523b72 | |||
| 6f7c3c4b47 | |||
| 4d9a7d6a5b | |||
| 9cf67e3637 | |||
| 3877609ba2 | |||
| 734e7e39cb | |||
| 2085bb7593 | |||
| 5b247a5a01 | |||
| 64f436c748 | |||
| b4e6313814 | |||
| a0cf895c8c | |||
| dec7d4a569 | |||
| fdc7550ff7 | |||
| 135dc9a8ab | |||
| 37a4fe400f | |||
| 7b466153a6 | |||
| baac42a0ad | |||
| fd05f5c8ff | |||
| 8bfdcd4295 | |||
| 90454807b4 | |||
| 77374fa820 | |||
| 8eda9d7e3d | |||
| 35cfe51c72 | |||
| 42c0fa02ae | |||
| 999a29cdf9 | |||
| 36edf4819b | |||
| dfac8a9da7 | |||
| 070ed94d18 | |||
| ea8938db72 | |||
| 4392b4610c | |||
| c96f2e300d | |||
| 94de7bae46 | |||
| ed1b06ba51 | |||
| 4b2c87a0bb | |||
| 0243fd063d | |||
| 4fed5e9a5a | |||
| 80a4915715 | |||
| 4716805f12 | |||
| 5490ff9be9 | |||
| c911862a24 | |||
| f823f7b18c | |||
| 02d1494ce9 | |||
| f69bf6af64 | |||
| 866d837cdf | |||
| 7d0a8cd6f8 | |||
| dfd3cdf890 | |||
| ef0288e075 | |||
| f219d6f6a5 | |||
| 710dfa2715 | |||
| 791625ff3b | |||
| 7e4cdde419 | |||
| 096e245d02 | |||
| 1879860f35 | |||
| ace5778817 | |||
| 12700a0500 | |||
| 6c8108f9dc | |||
| b27b2c31ce | |||
| 3d3adb564b | |||
| 925bee4af9 | |||
| adcee8c14d | |||
| 29126ac259 | |||
| bab8e08d2f | |||
| 4060ee9f57 | |||
| 5996772433 | |||
| cd45c77fac | |||
| e618d179fe | |||
| 4fa5f48c19 | |||
| 28993ae57d | |||
| 3d61bf87e7 | |||
| 911a4e1aa3 | |||
| beb38dec0e | |||
| 1fbf688897 | |||
| 5436868053 | |||
| 4977ca158f | |||
| 96214ac5f9 | |||
| 21fda909f5 | |||
| 8438d09b82 | |||
| 30a60b52cc | |||
| a93ed143fb | |||
| e054a884ef | |||
| 9bba508134 | |||
| 2d857d6d66 | |||
| 7286336b28 | |||
| 8a0bb6f846 | |||
| 142811c500 | |||
| 3b1cff9337 | |||
| efb4369c74 | |||
| 9d87a3b4ea | |||
| 4714b80631 | |||
| 3cba7363b4 | |||
| b2f592611d | |||
| 8786d0860a | |||
| 9100ccafb3 | |||
| 3fa048ddca | |||
| 8832baa05b | |||
| 11fdf7eaf3 | |||
| 059a86d393 | |||
| c1b4d93a0f | |||
| fb184ceebe | |||
| 69b50074ff | |||
| 7351cd671c | |||
| 26e112f894 | |||
| 6c732515d8 | |||
| 6892923743 | |||
| f8809ffac4 | |||
| 5da402bb2c | |||
| e962ce1e3b | |||
| 19df744bf1 | |||
| 69cc5a92c2 | |||
| cb22cc98f2 | |||
| 22cf5b69ba | |||
| a3fecd18e6 | |||
| 48c6f2999b | |||
| 91297ae63a | |||
| 6f61dcc083 | |||
| 24eca2fab8 | |||
| 9b3c36124f | |||
| 1b017e1dfc | |||
| 32b3aeb94c | |||
| 579d174288 | |||
| 1c850fc6bb | |||
| b35ecf8356 | |||
| 302f663a3f | |||
| c805f0f5aa | |||
| cb99f8b298 | |||
| 915a7efaf5 | |||
| 024e576b0f | |||
| f4a611e95f | |||
| c09e47f792 | |||
| dd6be33016 | |||
| 5af4fb6ba4 | |||
| 0ca4c7658f | |||
| 8cf59d6d2a | |||
| f3d068d6f2 | |||
| 1cfb4a2eae | |||
| 2b64b7981d | |||
| ad95dfdc8c | |||
| 988644159e | |||
| a9fa1ed4f4 | |||
| 3f125b30e1 | |||
| c9e7293a2a | |||
| f5d2c39c58 | |||
| 70bf8e8bb3 | |||
| f04053e1eb | |||
| af510706d7 | |||
| 29fbf7de31 | |||
| 102eef88a0 | |||
| b34d4652d3 | |||
| fcc2a4350a | |||
| 59183ce0a5 | |||
| b1f676d273 | |||
| b2784a1011 | |||
| d67e77c84d | |||
| d680f09932 | |||
| 30b5dd4c2f | |||
| 621f970998 | |||
| d6b70ad309 | |||
| 918e9131ad | |||
| 7f7f397062 | |||
| 3b1437e46c | |||
| 79899f7742 | |||
| 1496bab272 | |||
| 1ebbdb8ea9 | |||
| 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 |
@@ -0,0 +1,3 @@
|
||||
*.swift.gyb linguist-language=Swift
|
||||
|
||||
**/GENERATED-* linguist-generated=true
|
||||
@@ -0,0 +1,16 @@
|
||||
name: CocoaPods
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
validate_podspec:
|
||||
name: Run pod lib lint
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run pod lib lint
|
||||
run: pod lib lint --allow-warnings --verbose
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Compatibility tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: "0 9 * * 1" # Every Monday at 9:00 AM
|
||||
|
||||
jobs:
|
||||
compatibility_tests_macos:
|
||||
name: Execute compatibility tests
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run tests against Apple's Combine
|
||||
run: make test-compatibility
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
name: macOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
# This job is not a part of the macos_test job because of
|
||||
# the 'This copy of libswiftCore.dylib requires an OS version prior to 10.14.4.' error.
|
||||
# We have to invoke install_name_tool and patch the test executable
|
||||
# to work around this error.
|
||||
#
|
||||
# Other combinations of Xcode and macOS versions don't lead to this error.
|
||||
swift_5_0_test:
|
||||
name: Execute tests (macos-10.15, 10.3)
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: "10.3"
|
||||
- name: Swift version
|
||||
run: swift --version
|
||||
- name: Build and run tests in debug mode with coverage
|
||||
run: |
|
||||
swift build \
|
||||
--build-tests \
|
||||
-c debug \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
-Xswiftc -profile-generate \
|
||||
-Xswiftc -profile-coverage-mapping \
|
||||
--build-path .build-test-debug
|
||||
install_name_tool \
|
||||
-rpath /Applications/Xcode_10.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx /usr/lib/swift \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests
|
||||
install_name_tool \
|
||||
-add_rpath /Applications/Xcode_10.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests
|
||||
swift test \
|
||||
--skip-build \
|
||||
--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
|
||||
- name: Build and run tests in release mode
|
||||
run: |
|
||||
swift build \
|
||||
--build-tests \
|
||||
-c release \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
-Xswiftc -profile-generate \
|
||||
-Xswiftc -profile-coverage-mapping \
|
||||
--build-path .build-test-release
|
||||
install_name_tool \
|
||||
-rpath /Applications/Xcode_10.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx /usr/lib/swift \
|
||||
.build-test-release/release/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests
|
||||
install_name_tool \
|
||||
-add_rpath /Applications/Xcode_10.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \
|
||||
.build-test-release/release/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests
|
||||
swift test \
|
||||
--skip-build \
|
||||
-c release \
|
||||
--enable-code-coverage \
|
||||
--build-path .build-test-release
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
verbose: true
|
||||
macos_test:
|
||||
name: Execute tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-10.15
|
||||
xcode-version: "11.3.1" # Swift 5.1.3
|
||||
- os: macos-10.15
|
||||
xcode-version: "11.7" # Swift 5.2.4
|
||||
- os: macos-11
|
||||
xcode-version: "12.4" # Swift 5.3.2
|
||||
- os: macos-11
|
||||
xcode-version: "12.5.1" # Swift 5.4.2
|
||||
- os: macos-11
|
||||
xcode-version: "13.2.1" # Swift 5.5.2
|
||||
- os: macos-12
|
||||
xcode-version: "13.4.1" # Swift 5.6.1
|
||||
- os: macos-12
|
||||
xcode-version: "14.2" # Swift 5.7.2
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
- name: Swift version
|
||||
run: swift --version
|
||||
- name: Build and run tests in debug mode with coverage
|
||||
run: |
|
||||
swift test \
|
||||
-c debug \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
--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
|
||||
- name: Build and run tests in debug mode with TSan
|
||||
if: ${{ matrix.xcode-version != '13.2.1' && matrix.xcode-version != '13.4.1' }} # https://bugs.swift.org/browse/SR-15444
|
||||
run: |
|
||||
swift test \
|
||||
-c debug \
|
||||
--sanitize thread \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
--build-path .build-test-debug-sanitize-thread
|
||||
- name: Build and run tests in release mode
|
||||
run: |
|
||||
swift test \
|
||||
-c release \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
--enable-code-coverage \
|
||||
--build-path .build-test-release
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
verbose: true
|
||||
@@ -0,0 +1,26 @@
|
||||
name: SwiftLint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/swiftlint.yml"
|
||||
- ".swiftlint.yml"
|
||||
- "**/*.swift"
|
||||
|
||||
jobs:
|
||||
SwiftLint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
# Fetch current versions of files
|
||||
- name: Fetch base ref
|
||||
run: |
|
||||
git fetch --prune --no-tags --depth=1 origin +refs/heads/${{ github.base_ref }}:refs/heads/${{ github.base_ref }}
|
||||
# Diff pull request to current files, then SwiftLint changed files
|
||||
- name: GitHub Action for SwiftLint
|
||||
uses: mayk-it/action-swiftlint@3.2.2
|
||||
env:
|
||||
DIFF_BASE: ${{ github.base_ref }}
|
||||
DIFF_HEAD: HEAD
|
||||
with:
|
||||
args: --strict
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Ubuntu
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
ubuntu_test:
|
||||
name: Execute tests on Ubuntu
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
swift_version: ["5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7"]
|
||||
runs-on: ubuntu-latest
|
||||
container: swift:${{ matrix.swift_version }}-bionic
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generating LinuxMain.swift
|
||||
if: >-
|
||||
${{ matrix.swift_version == '5.0' ||
|
||||
matrix.swift_version == '5.1' ||
|
||||
matrix.swift_version == '5.2' ||
|
||||
matrix.swift_version == '5.3' }}
|
||||
run: |
|
||||
apt update -y
|
||||
apt upgrade -y
|
||||
apt install -y python3.8
|
||||
python3.8 utils/discover_tests.py
|
||||
- name: Building and running tests in debug mode with coverage
|
||||
run: |
|
||||
swift test \
|
||||
-c debug \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
--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
|
||||
- name: Building and running tests in debug mode with TSan
|
||||
if: ${{ matrix.swift_version != '5.0' }} # There are false positives there
|
||||
run: |
|
||||
swift test \
|
||||
-c debug \
|
||||
--sanitize thread \
|
||||
--build-path .build-test-debug-sanitize-thread
|
||||
- name: Building and running tests in release mode
|
||||
run: |
|
||||
swift test \
|
||||
-c release \
|
||||
-Xswiftc -warnings-as-errors \
|
||||
--build-path .build-test-release
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
verbose: true
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Wasm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
carton_wasmer_test_5_3:
|
||||
name: "Execute tests on Wasm (Swift 5.3)"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: swiftwasm/swiftwasm-action@v5.3
|
||||
|
||||
carton_wasmer_test_5_4:
|
||||
name: "Execute tests on Wasm (Swift 5.4)"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: swiftwasm/swiftwasm-action@v5.4
|
||||
|
||||
carton_wasmer_test_5_5:
|
||||
name: "Execute tests on Wasm (Swift 5.5)"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: swiftwasm/swiftwasm-action@v5.5
|
||||
|
||||
carton_wasmer_test_5_6:
|
||||
name: "Execute tests on Wasm (Swift 5.6)"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: swiftwasm/swiftwasm-action@v5.6
|
||||
|
||||
carton_wasmer_test_5_7:
|
||||
name: "Execute tests on Wasm (Swift 5.7)"
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: swiftwasm/swiftwasm-action@v5.7
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
windows_test:
|
||||
name: Execute tests on Windows
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2019
|
||||
swift_version: "5.4.2"
|
||||
- os: windows-2019
|
||||
swift_version: "5.5.1"
|
||||
- os: windows-2019
|
||||
swift_version: "5.6.1"
|
||||
- os: windows-2019
|
||||
swift_version: "5.7.2"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: compnerd/gha-setup-swift@main
|
||||
with:
|
||||
branch: swift-${{ matrix.swift_version }}-release
|
||||
tag: ${{ matrix.swift_version }}-RELEASE
|
||||
- name: Building and running tests in debug mode
|
||||
run: swift test
|
||||
+115
@@ -2,6 +2,8 @@
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
/.swiftpm
|
||||
Tests/LinuxMain.swift
|
||||
|
||||
# Created by https://www.gitignore.io/api/Xcode
|
||||
# Edit at https://www.gitignore.io/?templates=Xcode
|
||||
@@ -42,3 +44,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/
|
||||
|
||||
+12
-3
@@ -2,7 +2,10 @@ included:
|
||||
- Sources
|
||||
- Tests
|
||||
|
||||
child_config: Tests/.swiftlint.yml
|
||||
|
||||
disabled_rules:
|
||||
- blanket_disable_command
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
- colon
|
||||
@@ -16,11 +19,14 @@ disabled_rules:
|
||||
- identifier_name
|
||||
- nesting
|
||||
- notification_center_detachment
|
||||
- no_fallthrough_only
|
||||
- no_space_in_method_call
|
||||
- redundant_string_enum_value
|
||||
- todo
|
||||
- trailing_comma
|
||||
- type_body_length
|
||||
- opening_brace
|
||||
- untyped_error_in_catch
|
||||
|
||||
opt_in_rules:
|
||||
- array_init
|
||||
@@ -59,11 +65,14 @@ 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
|
||||
|
||||
implicit_return:
|
||||
included:
|
||||
- closure
|
||||
|
||||
line_length:
|
||||
warning: 90
|
||||
error: 120
|
||||
@@ -78,14 +87,14 @@ attributes:
|
||||
|
||||
custom_rules:
|
||||
no_foundation_dependency:
|
||||
included: Sources/OpenCombine
|
||||
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
|
||||
included: Sources/OpenCombine/
|
||||
name: "No Dispatch Dependency"
|
||||
regex: "^import.*Dispatch.*$"
|
||||
message: "We don't want to depend on Dispatch"
|
||||
|
||||
@@ -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>
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
language: generic
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
taps:
|
||||
- danger/tap
|
||||
packages:
|
||||
- swiftlint
|
||||
- danger-swift
|
||||
update: true
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- .build
|
||||
- ~/.danger-swift
|
||||
- ~/.swiftenv
|
||||
- ~/Library/Caches/Homebrew
|
||||
|
||||
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-09-05-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 | Code Quality"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: SWIFT_VERSION="5.0" RUN_DANGER="YES" SWIFT_LINT="YES"
|
||||
|
||||
before_cache:
|
||||
- brew cleanup
|
||||
|
||||
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
|
||||
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-debug;
|
||||
else
|
||||
make test-debug;
|
||||
fi
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-release;
|
||||
else
|
||||
make test-release;
|
||||
fi
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
|
||||
make generate-compatibility-xcodeproj;
|
||||
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
|
||||
- if [[ $RUN_DANGER == "YES" ]]; then
|
||||
danger-swift ci;
|
||||
fi
|
||||
after_success:
|
||||
- if [[ $CODE_COVERAGE == "YES" ]]; then
|
||||
make generate-xcodeproj;
|
||||
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
|
||||
bash <(curl -s https://codecov.io/bash);
|
||||
fi
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
# 0.14.0 (23 Apr 2023)
|
||||
This release is compatible with Xcode 14.2 and Swift 5.7
|
||||
|
||||
### Additions
|
||||
- Primary associated type support for `Publisher`, `Subscriber`, `ConnectablePublisher`, `Subject` and `Scheduler` protocols (#239)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed nullifying the reference to parent in `Future`'s conduit (#239)
|
||||
|
||||
# 0.13.0 (1 Feb 2022)
|
||||
This release is compatible with Xcode 13.2.
|
||||
|
||||
### Additions
|
||||
- Windows support (thank you @MaxDesiatov!)
|
||||
- `Publishers.Throttle` (#195, thank you @stuaustin)
|
||||
- `Publishers.PrefixUntilOutput` (#206)
|
||||
- `Publishers.Zip` (#222, thank you @MaxDesiatov and @ArthurChi)
|
||||
- `async`/`await` extensions: `Future.value` and `Publisher.values` (#219)
|
||||
|
||||
|
||||
### Bugfixes
|
||||
- Fixed reentrancy bugs in ` Subscribers.Sink` and `Subscribers.Assign` (#210)
|
||||
- Fixed lifecycle bugs in `Publishers.Concatenate` (#210)
|
||||
|
||||
# 0.12.0 (29 Jan 2021)
|
||||
|
||||
This release adds a new `OpenCombineShim` product that will conditionally re-export either
|
||||
Combine on Apple platforms, or OpenCombine on other platforms. Additionally, `ObservableObject`
|
||||
protocol is now available and working on all platforms.
|
||||
|
||||
A bug with `Timer(timeInterval:repeats:block:)` firing immediately not accounting for the passed
|
||||
`timeInterval` is fixed.
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix `Timer(timeInterval:repeats:block:)` not accounting `timeInterval` ([#196](https://github.com/OpenCombine/OpenCombine/pull/196)) via [@grigorye](https://github.com/grigorye)
|
||||
- Add `OpenCombineShim` product for easier importing ([#197](https://github.com/OpenCombine/OpenCombine/pull/197)) via [@MaxDesiatov](https://github.com/MaxDesiatov)
|
||||
- Implementation for `ObservableObject` with `Mirror` ([#201](https://github.com/OpenCombine/OpenCombine/pull/201)) via [@kateinoigakukun](https://github.com/kateinoigakukun)
|
||||
|
||||
# 0.11.0 (29 Oct 2020)
|
||||
|
||||
This release is compatible with Xcode 12.1.
|
||||
|
||||
### Additions
|
||||
- `Publisher.assigned(to:)` method that accepts a `Published.Publisher`.
|
||||
- New `Publisher.switchToLatest()` overloads.
|
||||
- New `Publisher.flatMap(maxPublishers:_:)` overloads.
|
||||
- `Optional.publisher` property.
|
||||
- New `_Introspection` protocol that allows to track and explore the subscription graph and data flow.
|
||||
|
||||
### Bugfixes
|
||||
- The project should now compile without warnings.
|
||||
- The following entities have been updated to match the behavior of the newest Combine version:
|
||||
- `Subscribers.Assign`
|
||||
- `Publishers.Breakpoint`
|
||||
- `Publishers.Buffer`
|
||||
- `CombineIdentifier`
|
||||
- `Publishers.CompactMap`
|
||||
- `Publishers.Concatenate`
|
||||
- `Publishers.Debounce`
|
||||
- `Publishers.Delay`
|
||||
- `DispatchQueue.SchedulerTimeType.Stride`
|
||||
- `Publishers.Drop`
|
||||
- `Publishers.Encode`
|
||||
- `Publishers.Decode`
|
||||
- `Publishers.Filter`
|
||||
- `Publishers.HandleEvents`
|
||||
- `Publishers.IgnoreOutput`
|
||||
- `Publishers.MeasureInterval`
|
||||
- `OperationQueue` scheduler
|
||||
- `Published`
|
||||
- `Publishers.ReceiveOn`
|
||||
- `Publishers.ReplaceError`
|
||||
- `RunLoop scheduler`
|
||||
- `Publishers.Sequence`
|
||||
- `Subscribers.Sink`
|
||||
- `Publishers.SubscribeOn`
|
||||
- `Publishers.Timeout`
|
||||
- `Timer` publisher
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.10.2 (23 Oct 2020)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed a crash caused by recursive acquisition of a non-recursive lock in SubbjectSubscriber (#186, thanks @stuaustin for the bug report)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.10.1 (4 Oct 2020)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed build errors on Linux with Swift 5.0 and Swift 5.3 toolchains (thanks, @adamleonard and @devmaximilian)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.10.0 (28 Jun 2020)
|
||||
|
||||
This release is compatible with Xcode 11.5.
|
||||
|
||||
### Additions
|
||||
- `Timer.publish(every:tolerance:on:in:options:)` (#156, thank you @MaxDesiatov)
|
||||
- `OperationQueue` scheduler (#165)
|
||||
- `Publishers.Timeout` (#164)
|
||||
- `Publishers.Debounce` (#133)
|
||||
|
||||
### Bugfixes
|
||||
- `PassthroughSubject`, `CurrentValueSubject` and `Future` have been rewritten from scratch. They are now faster, more correct and no longer leak subscriptions (#170).
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.9.0 (12 Jun 2020)
|
||||
|
||||
This release is compatible with Xcode 11.5.
|
||||
|
||||
### Additions
|
||||
- The `Subscribers.Demand` struct can be nicely formatted in LLDB (#146, thank you @mayoff).
|
||||
- `Publishers.SwitchToLatest` (#142).
|
||||
- The `RunLoop` scheduler in `OpenCombineFoundation` (#131).
|
||||
- `Publishers.Catch` and `Publishers.TryCatch` (#140).
|
||||
|
||||
### Bugfixes
|
||||
- Worked around a [bug in the Swift compiler](https://bugs.swift.org/browse/SR-11680) when building the `COpenCombineHelpers` target (#145, thank you @mayoff).
|
||||
- Improved documentation.
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
|
||||
|
||||
# 0.8.0 (17 Jan 2020)
|
||||
|
||||
This release is compatible with Xcode 11.3.1.
|
||||
|
||||
### Additions
|
||||
- `Publishers.ReplaceEmpty` (#122, thank you @spadafiva)
|
||||
- `NotificationCenter.Publisher` (#84)
|
||||
- `URLSession.DataTaskPublisher` (#127)
|
||||
- `Publishers.DropUntilOutput` (#136)
|
||||
- `Publishers.CollectByCount` (#137)
|
||||
- `Publishers.AssertNoFailure` (#138)
|
||||
- `Publishers.Buffer` (#143)
|
||||
|
||||
### Bugfixes
|
||||
- Fixed integer overflows in `DispatchQueue.SchedulerTimeType.Stride` (#126, #130)
|
||||
- Fixed the 'default will never be executed' warning on non-Darwin platforms (like Linux) (#129)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
|
||||
|
||||
# 0.7.0 (10 Dec 2019)
|
||||
|
||||
This release is compatible with Xcode 11.2.1.
|
||||
|
||||
### Additions
|
||||
- `Publishers.Delay` (#114)
|
||||
- `Publishers.ReceiveOn` (#115)
|
||||
- `Publishers.SubscribeOn` (#116)
|
||||
- `Publishers.MeasureInterval` (#117)
|
||||
- `Publishers.Breakpoint` (#118)
|
||||
- `Publishers.HandleEvents` (#118)
|
||||
- `Publishers.Concatenate` (#90)
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
|
||||
|
||||
# 0.6.0 (26 Nov 2019)
|
||||
|
||||
This release is compatible with Xcode 11.2.1.
|
||||
|
||||
### Thread safety
|
||||
- `Publishers.IgnoreOutput` has been audited for thread safety (#88)
|
||||
- `Publishers.DropWhile` and `Publishers.TryDropWhile` have been audited for thread safety (#87)
|
||||
|
||||
### Additions
|
||||
- `Publishers.Output` (#91)
|
||||
- `Record` (#100)
|
||||
- `Publishers.RemoveDuplicates`, `Publishers.TryRemoveDuplicates` (#89)
|
||||
- `Publishers.PrefixWhile`, `Publishers.TryPrefixWhile` (#89)
|
||||
- `Future` (#107, thanks @MaxDesiatov!)
|
||||
|
||||
### Bugfixes
|
||||
- The behavior of the `Publishers.Encode` and `Publishers.Decode` subscriptions is fixed (#112)
|
||||
- The behavior of the `Publishers.IgnoreOutput` subscription is fixed (#88)
|
||||
- The behavior of the `Publishers.Print` subscription is fixed (#92)
|
||||
- The behavior of the `Publishers.ReplaceError` subscription is fixed (#89)
|
||||
- The behavior of the `Publishers.Filter` and `Publishers.TryFilter` subscriptions is fixed (#89)
|
||||
- The behavior of the `Publishers.CompactMap` and `Publishers.TryCompactMap` subscriptions is fixed (#89)
|
||||
- The behavior of the `Publishers.Multicast` subscription is fixed (#110)
|
||||
- `Publishers.FlatMap` is reimplemented from scratch. Its behavior is fixed in many ways, it now fully matches that of Combine (#89)
|
||||
- `@Published` property wrapper is fixed! (#112)
|
||||
- The behavior of `DispatchQueue.SchedulerTimeType` is fixed to match that of the latest SDKs (#96)
|
||||
- OpenCombine is now usable on 32 bit platforms. Why? Because we can.
|
||||
|
||||
|
||||
### Known issues
|
||||
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
|
||||
|
||||
# 0.5.0 (17 Oct 2019)
|
||||
|
||||
This release is compatible with Xcode 11.1.
|
||||
|
||||
### Additions
|
||||
- `Publishers.MapKeyPath` (#71)
|
||||
- `Publishers.Reduce` (#76)
|
||||
- `Publishers.TryReduce` (#76)
|
||||
- `Publishers.Last` (#76)
|
||||
- `Publishers.LastWhere` (#76)
|
||||
- `Publishers.TryLastWhere` (#76)
|
||||
- `Publishers.AllSatisfy` (#76)
|
||||
- `Publishers.TryAllSatisfy` (#76)
|
||||
- `Publishers.Contains` (#76)
|
||||
- `Publishers.ContainsWhere` (#76)
|
||||
- `Publishers.TryContainsWhere` (#76)
|
||||
- `Publishers.Collect` (#76)
|
||||
- `Publishers.Comparison` (#76)
|
||||
- `Publishers.Drop` (#70, thank you @5sw!)
|
||||
- `Publishers.Scan` (#83, thank you @epatey!)
|
||||
- `Publishers.TryScan` (#83, thank you @epatey!)
|
||||
|
||||
### Bugfixes
|
||||
- `Publishers.Print` doesn't print a redundant whitespace anymore.
|
||||
|
||||
### Known issues
|
||||
- `@Published` property wrapper doesn't work yet
|
||||
|
||||
# 0.4.0 (8 Oct 2019)
|
||||
|
||||
This release is compatible with Xcode 11.1.
|
||||
|
||||
### Thread safety
|
||||
- `SubjectSubscriber` (which is used when you subscribe a subject to a publisher) has been audited for thread-safety
|
||||
- `Publishers.Multicast` has been audited for thread safety (#63)
|
||||
- `Publishers.TryMap` has been audited for thread safety
|
||||
- `Just` has been audited for thread safety
|
||||
- `Optional.Publisher` has been audited for thread safety
|
||||
- `Publishers.Sequence` has been audited for thread safety
|
||||
- `Publishers.ReplaceError` has been audited for thread safety
|
||||
- `Subscribers.Assign` has been audited for thread safety
|
||||
- `Subscribers.Sink` has been audited for thread safety
|
||||
|
||||
### Bugfixes
|
||||
- The semantics of `Publishers.Print`, `Publishers.TryMap` have been fixed
|
||||
- Fix `iterator.next()` being called twice in `Publishers.Sequence` (#62)
|
||||
- The default initializer of `CombineIdentifier` (the one that takes no arguments) is now much faster (#66, #69)
|
||||
- When `Publishers.Sequence` subscription is cancelled while it emits values, the cancellation is respected (#73, thanks @5sw!)
|
||||
|
||||
### Additions
|
||||
- `DispatchQueueScheduler` (#46)
|
||||
- `Equatable` conformances for `First`, `ReplaceError`
|
||||
- Added `eraseToAnyPublisher()` method (#59, thanks @evyasafhouzz for reporting!)
|
||||
- `Publishers.MakeConnectable` (#61)
|
||||
- `Publishers.Autoconnect` (#60)
|
||||
- `Publishers.Share` (#60)
|
||||
|
||||
### Known issues
|
||||
- `@Published` property wrapper doesn't work yet
|
||||
|
||||
# 0.3.0 (13 Sep 2019)
|
||||
|
||||
Among other things this release is compatible with Xcode 11.0 GM seed.
|
||||
|
||||
### Bugfixes
|
||||
- Store newly send value in internal variable inside CurrentValueObject (#39, thanks @FranzBusch!)
|
||||
|
||||
### Additions
|
||||
- `Filter`/`TryFilter` (#22, thanks @spadafiva!)
|
||||
- `First`/`FirstWhere`/`TryFirstWhere` (#22, thanks again @spadafiva!)
|
||||
- `CompactMap`/`TryCompacrMap` (#32)
|
||||
- `IgnoreOutput` (#44, thanks @epatey!)
|
||||
- `ReplaceError` (#50, thanks @vladiulianbogdan!)
|
||||
- `FlatMap` (#45, thanks again @epatey!)
|
||||
|
||||
### Known issues
|
||||
- `@Published` property wrapper doesn't work yet
|
||||
|
||||
# 0.2.0 (31 Jul 2019)
|
||||
|
||||
Updated for the newest Xcode 11.0 beta 5
|
||||
|
||||
# 0.1.0 (4 Jul 2019)
|
||||
|
||||
The first pre-pre-pre-alpha release is here!
|
||||
|
||||
Lots of stuff still unimplemented.
|
||||
|
||||
For now we have:
|
||||
|
||||
- `Just`
|
||||
- `Publishers.Decode`
|
||||
- `Publishers.DropWhile`
|
||||
- `Publishers.Empty`
|
||||
- `Publishers.Encode`
|
||||
- `Publishers.Fail`
|
||||
- `Publishers.Map`
|
||||
- `Publishers.Multicast`
|
||||
- `Publishers.Once`
|
||||
- `Publishers.Optional`
|
||||
- `Publishers.Print`
|
||||
- `Publishers.Sequence`
|
||||
- `Subscribers.Assign`
|
||||
- `Subscribers.Completion`
|
||||
- `Subscribers.Demand`
|
||||
- `Subscribers.Sink`
|
||||
- `AnyCancellable`
|
||||
- `AnyPublisher`
|
||||
- `AnySubject`
|
||||
- `AnySubscriber`
|
||||
- `Cancellable`
|
||||
- `CombineIdentifier`
|
||||
- `ConnectablePublisher`
|
||||
- `CurrentValueSubject`
|
||||
- `CustomCombineIdentifierConvertible`
|
||||
- `ImmediateScheduler`
|
||||
- `PassthroughSubject`
|
||||
- `Publisher`
|
||||
- `Result`
|
||||
- `Scheduler`
|
||||
- `Subject`
|
||||
- `Subscriber`
|
||||
- `Subscription`
|
||||
@@ -0,0 +1,60 @@
|
||||
# Contributing
|
||||
|
||||
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
|
||||
|
||||
Please refer to the [issue #1](https://github.com/OpenCombine/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/OpenCombine/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
|
||||
|
||||
You can refer to [this repo](https://github.com/OpenCombine/combine-interfaces) to observe Apple's Combine API and documentation changes between different Xcode (beta) versions.
|
||||
|
||||
You can 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:
|
||||
|
||||
```
|
||||
$ make test-compatibility
|
||||
```
|
||||
|
||||
Or enable the `-DOPENCOMBINE_COMPATIBILITY_TEST` compiler flag in Xcode's build settings. Note that on iOS only the latter will work.
|
||||
|
||||
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/OpenCombine/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
|
||||
|
||||
#### Releasing a new version
|
||||
|
||||
1. Create a new branch from master and call it `release/<major>.<minor>.<patch>`.
|
||||
1. Replace the usages of the old version in `README.md` with the new version (make sure to check the [Swift Package Manager](#swift-package-manager) and [CocoaPods](#cocoapods) sections).
|
||||
1. Bump the version in `OpenCombine.podspec`, `OpenCombineDispatch.podspec` and `OpenCombineFoundation.podspec`. In the latter two you will also need to set the `spec.dependency "OpenCombine"` property to the **previous** version. Why? Because otherwise the `pod lib lint` command that we run on our regular CI will fail when validating the `OpenCombineDispatch` and `OpenCombineFoundation` podspecs, since the dependencies are not yet in the trunk. If we set the dependencies to the previous version (which is already in the trunk), everything will be fine. This is purely to make the CI work. The clients will not experience any issues, since the version is specified as `>=`.
|
||||
1. Create a pull request to master for the release branch and make sure the CI passes.
|
||||
1. Merge the pull request.
|
||||
1. In the GitHub web interface on the [releases](https://github.com/OpenCombine/OpenCombine/releases) page, click the **Draft a new release** button.
|
||||
1. The **Tag version** and **Release title** fields should be filled with the version number.
|
||||
1. The description of the release should be consistent with the previous releases. It is a good practice to divide the description into several sections: additions, bugfixes, known issues etc. Also, be sure to mention the nicknames of the contributors of the new release.
|
||||
1. Publish the release.
|
||||
1. Switch to the master branch and pull the changes.
|
||||
1. Push the release to CocoaPods trunk. For that, execute the following commands:
|
||||
|
||||
```
|
||||
pod trunk push OpenCombine.podspec --verbose --allow-warnings
|
||||
pod trunk push OpenCombineDispatch.podspec --verbose --allow-warnings
|
||||
pod trunk push OpenCombineFoundation.podspec --verbose --allow-warnings
|
||||
```
|
||||
|
||||
Note that you need to be one of the owners of the pod for that.
|
||||
|
||||
#### 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`.
|
||||
@@ -1,12 +0,0 @@
|
||||
import Danger
|
||||
|
||||
let danger = Danger()
|
||||
|
||||
SwiftLint.lint(inline: true,
|
||||
configFile: ".swiftlint.yml",
|
||||
strict: true,
|
||||
lintAllFiles: true)
|
||||
|
||||
if danger.warnings.isEmpty, danger.fails.isEmpty {
|
||||
markdown("LGTM")
|
||||
}
|
||||
@@ -1,29 +1,37 @@
|
||||
SWIFT_EXE=swift
|
||||
SWIFT_TEST_FLAGS=
|
||||
SWIFT_BUILD_FLAGS=-Xcc -Wunguarded-availability
|
||||
|
||||
debug:
|
||||
swift build -c debug
|
||||
$(SWIFT_EXE) build -c debug $(SWIFT_BUILD_FLAGS)
|
||||
|
||||
release:
|
||||
swift build -c release
|
||||
$(SWIFT_EXE) build -c release $(SWIFT_BUILD_FLAGS)
|
||||
|
||||
test-debug:
|
||||
swift test -c debug --sanitize thread $(SWIFT_TEST_FLAGS)
|
||||
$(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 test -c release $(SWIFT_TEST_FLAGS)
|
||||
$(SWIFT_EXE) test -c release $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
|
||||
|
||||
swift-version:
|
||||
swift -version
|
||||
$(SWIFT_EXE) -version
|
||||
|
||||
test-compatibility:
|
||||
swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
$(SWIFT_EXE) test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
|
||||
generate-compatibility-xcodeproj:
|
||||
swift package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
|
||||
$(SWIFT_EXE) package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
|
||||
open OpenCombine.xcodeproj
|
||||
|
||||
generate-xcodeproj:
|
||||
swift package generate-xcodeproj --enable-code-coverage
|
||||
$(SWIFT_EXE) package $(SWIFT_BUILD_FLAGS) generate-xcodeproj --enable-code-coverage
|
||||
|
||||
gyb:
|
||||
$(shell ./utils/recursively_gyb.sh)
|
||||
|
||||
clean:
|
||||
rm -rf .build
|
||||
@@ -35,4 +43,5 @@ clean:
|
||||
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.14.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/OpenCombine/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/OpenCombine/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.14.0"
|
||||
spec.summary = "OpenCombine + Dispatch interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
Extends `DispatchQueue` with conformance to the `Scheduler` protocol
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/OpenCombine/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/OpenCombine/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.13.0'
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineFoundation"
|
||||
spec.version = "0.14.0"
|
||||
spec.summary = "OpenCombine + OpenCombineFoundation interoperability"
|
||||
|
||||
spec.description = <<-DESC
|
||||
Adds publishers to Foundation types like NotificationCenter, URLSession etc.
|
||||
DESC
|
||||
|
||||
spec.homepage = "https://github.com/OpenCombine/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/OpenCombine/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.13.0'
|
||||
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
|
||||
}
|
||||
+78
-9
@@ -1,19 +1,88 @@
|
||||
// swift-tools-version:5.0
|
||||
// swift-tools-version:5.5
|
||||
|
||||
import PackageDescription
|
||||
|
||||
// This list should be updated whenever SwiftPM adds support for a new platform.
|
||||
// See: https://bugs.swift.org/browse/SR-13814
|
||||
let supportedPlatforms: [Platform] = [
|
||||
.macOS,
|
||||
.macCatalyst,
|
||||
.iOS,
|
||||
.watchOS,
|
||||
.tvOS,
|
||||
.driverKit,
|
||||
.linux,
|
||||
.android,
|
||||
.windows,
|
||||
.wasi,
|
||||
]
|
||||
|
||||
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"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "OpenCombine"),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine", "GottaGoFast"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
]
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(
|
||||
name: "OpenCombine",
|
||||
dependencies: [
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
],
|
||||
exclude: [
|
||||
"RootProtocols.swift.gyb",
|
||||
"Concurrency/Publisher+Concurrency.swift.gyb",
|
||||
"Publishers/Publishers.Encode.swift.gyb",
|
||||
"Publishers/Publishers.MapKeyPath.swift.gyb",
|
||||
"Publishers/Publishers.Catch.swift.gyb"
|
||||
],
|
||||
swiftSettings: [.define("WASI", .when(platforms: [.wasi]))]
|
||||
),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(
|
||||
name: "OpenCombineFoundation",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "OpenCombineTests",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-enable-testing"]),
|
||||
.define("WASI", .when(platforms: [.wasi]))
|
||||
]
|
||||
)
|
||||
],
|
||||
cxxLanguageStandard: .cxx17
|
||||
)
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension Array where Element == Platform {
|
||||
func except(_ exceptions: [Platform]) -> [Platform] {
|
||||
return filter { !exceptions.contains($0) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version:5.0
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation",
|
||||
]
|
||||
),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version:5.1
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation",
|
||||
]
|
||||
),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version:5.2
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
|
||||
"COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation",
|
||||
]
|
||||
),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
// swift-tools-version:5.3
|
||||
|
||||
import PackageDescription
|
||||
|
||||
// This list should be updated whenever SwiftPM adds support for a new platform.
|
||||
// See: https://bugs.swift.org/browse/SR-13814
|
||||
let supportedPlatforms: [Platform] = [
|
||||
.macOS,
|
||||
.iOS,
|
||||
.watchOS,
|
||||
.tvOS,
|
||||
.linux,
|
||||
.android,
|
||||
// Disable Windows because of https://bugs.swift.org/browse/SR-13817
|
||||
// .windows,
|
||||
.wasi,
|
||||
]
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(
|
||||
name: "OpenCombine",
|
||||
dependencies: [
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
],
|
||||
exclude: [
|
||||
"RootProtocols.swift.gyb",
|
||||
"Concurrency/Publisher+Concurrency.swift.gyb",
|
||||
"Publishers/Publishers.Encode.swift.gyb",
|
||||
"Publishers/Publishers.MapKeyPath.swift.gyb",
|
||||
"Publishers/Publishers.Catch.swift.gyb"
|
||||
],
|
||||
swiftSettings: [.define("WASI", .when(platforms: [.wasi]))]
|
||||
),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(
|
||||
name: "OpenCombineFoundation",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "OpenCombineTests",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-enable-testing"]),
|
||||
.define("WASI", .when(platforms: [.wasi]))
|
||||
]
|
||||
)
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension Array where Element == Platform {
|
||||
func except(_ exceptions: [Platform]) -> [Platform] {
|
||||
// See: https://bugs.swift.org/browse/SR-13813
|
||||
let exceptionsDescriptions = exceptions.map(String.init(describing:))
|
||||
return filter { platform in
|
||||
!exceptionsDescriptions.contains(String(describing: platform))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// swift-tools-version:5.4
|
||||
|
||||
import PackageDescription
|
||||
|
||||
// This list should be updated whenever SwiftPM adds support for a new platform.
|
||||
// See: https://bugs.swift.org/browse/SR-13814
|
||||
let supportedPlatforms: [Platform] = [
|
||||
.macOS,
|
||||
.iOS,
|
||||
.watchOS,
|
||||
.tvOS,
|
||||
.linux,
|
||||
.android,
|
||||
.windows,
|
||||
.wasi,
|
||||
]
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(
|
||||
name: "OpenCombine",
|
||||
dependencies: [
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
],
|
||||
exclude: [
|
||||
"RootProtocols.swift.gyb",
|
||||
"Concurrency/Publisher+Concurrency.swift.gyb",
|
||||
"Publishers/Publishers.Encode.swift.gyb",
|
||||
"Publishers/Publishers.MapKeyPath.swift.gyb",
|
||||
"Publishers/Publishers.Catch.swift.gyb"
|
||||
],
|
||||
swiftSettings: [.define("WASI", .when(platforms: [.wasi]))]
|
||||
),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.target(
|
||||
name: "OpenCombineFoundation",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "OpenCombineTests",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-enable-testing"]),
|
||||
.define("WASI", .when(platforms: [.wasi]))
|
||||
]
|
||||
)
|
||||
],
|
||||
cxxLanguageStandard: .cxx17
|
||||
)
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension Array where Element == Platform {
|
||||
func except(_ exceptions: [Platform]) -> [Platform] {
|
||||
return filter { !exceptions.contains($0) }
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,83 @@
|
||||
# OpenCombine
|
||||
[](https://travis-ci.org/broadwaylamb/OpenCombine)
|
||||
[](https://codecov.io/gh/broadwaylamb/OpenCombine)
|
||||
[](https://codecov.io/gh/OpenCombine/OpenCombine)
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/slack-OpenCombine-yellow.svg?logo=slack">](https://join.slack.com/t/opencombine/shared_invite/enQtNzE2MjE5NzkxODI0LTYxMjkzNDUxZWViZWI1Njc2YjBhODgxNjRjOTdkZTcxOGU2ZjJjZjYxMGI3NWZkN2RkNGFmZTUzNmU3MGE2ZWM)
|
||||
[](https://cocoapods.org/pods/OpenCombine)
|
||||
|
||||
Open-source implementation of Apple's [Combine](https://developer.apple.com/documentation/combine) framework for processing values over time.
|
||||
|
||||
The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux and Windows.
|
||||
The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux, Windows and WebAssembly.
|
||||
|
||||
The project is in early development.
|
||||
| **CI Status** |
|
||||
|---|
|
||||
|[](https://github.com/OpenCombine/OpenCombine/actions/workflows/compatibility_tests.yml)|
|
||||
|[](https://github.com/OpenCombine/OpenCombine/actions/workflows/macos.yml)|
|
||||
|[](https://github.com/OpenCombine/OpenCombine/actions/workflows/ubuntu.yml)|
|
||||
|[](https://github.com/OpenCombine/OpenCombine/actions/workflows/windows.yml)|
|
||||
|[](https://github.com/OpenCombine/OpenCombine/actions/workflows/wasm.yml)|
|
||||
|
||||
|
||||
### Installation
|
||||
`OpenCombine` contains three public targets: `OpenCombine`, `OpenCombineFoundation` and `OpenCombineDispatch` (the fourth one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
|
||||
|
||||
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`. The same applies to Foundation: if you want to use, for instance, `NotificationCenter` or `URLSession` publishers, you'll need to also import `OpenCombineFoundation`.
|
||||
|
||||
If you develop code for multiple platforms, you may find it more convenient to import the
|
||||
`OpenCombineShim` module instead. It conditionally re-exports Combine on Apple platforms (if
|
||||
available), and all OpenCombine modules on other platforms. You can import `OpenCombineShim` only
|
||||
when using SwiftPM. It is not currently available for CocoaPods.
|
||||
|
||||
##### Swift Package Manager
|
||||
###### Swift Package
|
||||
To add `OpenCombine` to your [SwiftPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file. `OpenCombineDispatch` and `OpenCombineFoundation` products are currently not supported on WebAssembly. If your project targets WebAssembly exclusively, you should omit them from the list of your dependencies. If it targets multiple platforms including WebAssembly, depend on them only on non-WebAssembly platforms with [conditional target dependencies](https://github.com/apple/swift-evolution/blob/main/proposals/0273-swiftpm-conditional-target-dependencies.md).
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.14.0")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "MyAwesomePackage",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.product(name: "OpenCombineFoundation", package: "OpenCombine"),
|
||||
.product(name: "OpenCombineDispatch", package: "OpenCombine")
|
||||
]
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
###### Xcode
|
||||
`OpenCombine` can also be added as a SwiftPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
|
||||
|
||||
To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Dependency…**, enter the [repository URL](https://github.com/OpenCombine/OpenCombine.git), choose the latest available version, and activate the checkboxes:
|
||||
|
||||
<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.14.0'
|
||||
pod 'OpenCombineDispatch', '~> 0.14.0'
|
||||
pod 'OpenCombineFoundation', '~> 0.14.0'
|
||||
```
|
||||
|
||||
#### Debugger Support
|
||||
|
||||
The file `opencombine_lldb.py` defines some `lldb` type summaries for easier debugging. These type summaries improve the way `lldb` and Xcode display some OpenCombine values.
|
||||
|
||||
To use `opencombine_lldb.py`, figure out its full path. Let's say the full path is `~/projects/OpenCombine/opencombine_lldb.py`. Then the following statement to your `~/.lldbinit` file:
|
||||
|
||||
command script import ~/projects/OpenCombine/opencombine_lldb.py
|
||||
|
||||
Currently, `opencombine_lldb.py` defines type summaries for these types:
|
||||
|
||||
- `Subscribers.Demand`
|
||||
- That's all for now.
|
||||
|
||||
### Contributing
|
||||
|
||||
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
|
||||
|
||||
Please refer to the [issue #1](https://github.com/broadwaylamb/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/broadwaylamb/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
|
||||
|
||||
You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff851c4c1dbf0a872e26) to observe Apple's Combine API changes between different Xcode (beta) versions, or to [this gist](https://gist.github.com/broadwaylamb/82dc2ce4ffbe06527c2c352b8f10910f) to see the relevant contents of the .swiftinterface file for Combine.
|
||||
|
||||
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
|
||||
|
||||
```
|
||||
$ swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
```
|
||||
|
||||
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.
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
+68
-2559
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
//
|
||||
// COpenCombineHelpers.cpp
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23/09/2019.
|
||||
//
|
||||
|
||||
#include "COpenCombineHelpers.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <system_error>
|
||||
|
||||
#if __has_include(<pthread.h>)
|
||||
# include <pthread.h>
|
||||
# define OPENCOMBINE_HAS_PTHREAD 1
|
||||
#else
|
||||
# define OPENCOMBINE_HAS_PTHREAD 0
|
||||
#endif
|
||||
|
||||
#if __has_include(<signal.h>)
|
||||
# include <signal.h>
|
||||
# define OPENCOMBINE_HAS_SIGNAL_HANDLING 1
|
||||
#else
|
||||
# define OPENCOMBINE_HAS_SIGNAL_HANDLING 0
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <os/lock.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
#include <mutex>
|
||||
|
||||
// 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() {}
|
||||
};
|
||||
|
||||
#if OPENCOMBINE_HAS_PTHREAD
|
||||
class PThreadMutex final : 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 {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_lock(&mutex_));
|
||||
}
|
||||
|
||||
void unlock() override {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_unlock(&mutex_));
|
||||
}
|
||||
|
||||
~PThreadMutex() {
|
||||
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() {
|
||||
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()));
|
||||
}
|
||||
};
|
||||
#endif // OPENCOMBINE_HAS_PTHREAD
|
||||
|
||||
#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__
|
||||
|
||||
template <typename Mu>
|
||||
class GenericMutex final : PlatformIndependentMutex {
|
||||
Mu mutex_;
|
||||
public:
|
||||
|
||||
GenericMutex() = default;
|
||||
|
||||
GenericMutex(const GenericMutex&) = delete;
|
||||
GenericMutex& operator=(const GenericMutex&) = delete;
|
||||
|
||||
GenericMutex(GenericMutex&&) = delete;
|
||||
GenericMutex& operator=(GenericMutex&&) = delete;
|
||||
|
||||
void lock() override {
|
||||
mutex_.lock();
|
||||
}
|
||||
|
||||
void unlock() override {
|
||||
mutex_.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
using StdMutex = GenericMutex<std::mutex>;
|
||||
using StdRecursiveMutex = GenericMutex<std::recursive_mutex>;
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
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};
|
||||
}
|
||||
#elif OPENCOMBINE_HAS_PTHREAD
|
||||
// When possible, use pthread mutex implementation, because it allows
|
||||
// setting the PTHREAD_MUTEX_ERRORCHECK attribute, which makes
|
||||
// recursive locking a hard error instead of UB.
|
||||
return {new PThreadMutex};
|
||||
#else
|
||||
return {new StdMutex};
|
||||
#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 StdRecursiveMutex};
|
||||
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);
|
||||
}
|
||||
|
||||
void opencombine_stop_in_debugger(void) {
|
||||
#if _WIN32
|
||||
DebugBreak();
|
||||
#elif OPENCOMBINE_HAS_SIGNAL_HANDLING
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// COpenCombineHelpers.h
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23/09/2019.
|
||||
//
|
||||
|
||||
#ifndef COPENCOMBINEHELPERS_H
|
||||
#define COPENCOMBINEHELPERS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __has_attribute(swift_name)
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
|
||||
#else
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name)
|
||||
#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
|
||||
|
||||
void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* COPENCOMBINEHELPERS_H */
|
||||
@@ -10,6 +10,8 @@
|
||||
/// Subscriber implementations can use this type to provide a “cancellation token” that
|
||||
/// makes it possible for a caller to cancel a publisher, but not to use the
|
||||
/// `Subscription` object to request items.
|
||||
///
|
||||
/// An `AnyCancellable` instance automatically calls `cancel()` when deinitialized.
|
||||
public final class AnyCancellable: Cancellable, Hashable {
|
||||
|
||||
private var _cancel: (() -> Void)?
|
||||
@@ -45,18 +47,18 @@ public final class AnyCancellable: Cancellable, Hashable {
|
||||
|
||||
extension AnyCancellable {
|
||||
|
||||
/// Stores this AnyCancellable in the specified collection.
|
||||
/// Parameters:
|
||||
/// - collection: The collection to store this AnyCancellable.
|
||||
/// Stores this type-erasing cancellable instance in the specified collection.
|
||||
///
|
||||
/// - Parameter collection: The collection in which to store this `AnyCancellable`.
|
||||
public func store<Cancellables: RangeReplaceableCollection>(
|
||||
in collection: inout Cancellables
|
||||
) where Cancellables.Element == AnyCancellable {
|
||||
collection.append(self)
|
||||
}
|
||||
|
||||
/// Stores this AnyCancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - collection: The set to store this AnyCancellable.
|
||||
/// Stores this type-erasing cancellable instance in the specified collection.
|
||||
///
|
||||
/// - Parameter collection: The collection in which to store this `AnyCancellable`.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
set.insert(self)
|
||||
}
|
||||
|
||||
@@ -5,27 +5,83 @@
|
||||
// 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.
|
||||
/// This form of _type erasure_ preserves abstraction across API boundaries, such as
|
||||
/// different modules.
|
||||
/// When you expose your publishers as the `AnyPublisher` type, you can change
|
||||
/// the underlying implementation over time without affecting existing clients.
|
||||
///
|
||||
/// The following example shows two types that each have a `publisher` property.
|
||||
/// `TypeWithSubject` exposes this property as its actual type, `PassthroughSubject`,
|
||||
/// while `TypeWithErasedSubject` uses `eraseToAnyPublisher()` to expose it as
|
||||
/// an `AnyPublisher`. As seen in the output, a caller from another module can access
|
||||
/// `TypeWithSubject.publisher` as its native type. This means you can’t change your
|
||||
/// publisher to a different type without breaking the caller. By comparison,
|
||||
/// `TypeWithErasedSubject.publisher` appears to callers as an `AnyPublisher`, so you
|
||||
/// can change the underlying publisher type at will.
|
||||
///
|
||||
/// public class TypeWithSubject {
|
||||
/// public let publisher: some Publisher = PassthroughSubject<Int,Never>()
|
||||
/// }
|
||||
/// public class TypeWithErasedSubject {
|
||||
/// public let publisher: some Publisher = PassthroughSubject<Int,Never>()
|
||||
/// .eraseToAnyPublisher()
|
||||
/// }
|
||||
///
|
||||
/// // In another module:
|
||||
/// let nonErased = TypeWithSubject()
|
||||
/// if let subject = nonErased.publisher as? PassthroughSubject<Int,Never> {
|
||||
/// print("Successfully cast nonErased.publisher.")
|
||||
/// }
|
||||
/// let erased = TypeWithErasedSubject()
|
||||
/// if let subject = erased.publisher as? PassthroughSubject<Int,Never> {
|
||||
/// print("Successfully cast erased.publisher.")
|
||||
/// }
|
||||
///
|
||||
/// // Prints "Successfully cast nonErased.publisher."
|
||||
///
|
||||
/// - Returns: An ``AnyPublisher`` wrapping this publisher.
|
||||
@inlinable
|
||||
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing publisher.
|
||||
///
|
||||
/// Use `AnyPublisher` to wrap a publisher whose type has details you don’t want to expose
|
||||
/// to subscribers or other publishers.
|
||||
/// across API boundaries, such as different modules. Wrapping a `Subject` with
|
||||
/// `AnyPublisher` also prevents callers from accessing its `send(_:)` method. When you
|
||||
/// use type erasure this way, you can change the underlying publisher implementation over
|
||||
/// time without affecting existing clients.
|
||||
///
|
||||
/// You can use OpenCombine’s `eraseToAnyPublisher()` operator to wrap a publisher with
|
||||
/// `AnyPublisher`.
|
||||
public struct AnyPublisher<Output, Failure: Error>
|
||||
: CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
|
||||
@usableFromInline
|
||||
internal let box: PublisherBoxBase<Output, Failure>
|
||||
|
||||
/// Creates a type-erasing publisher to wrap the provided publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher: A publisher to wrap with a type-eraser.
|
||||
/// - Parameter publisher: A publisher to wrap with a type-eraser.
|
||||
@inlinable
|
||||
public init<PublisherType: Publisher>(_ publisher: PublisherType)
|
||||
where Output == PublisherType.Output, Failure == PublisherType.Failure
|
||||
{
|
||||
box = PublisherBox(base: publisher)
|
||||
// If this has already been boxed, avoid boxing again
|
||||
if let erased = publisher as? AnyPublisher<Output, Failure> {
|
||||
box = erased.box
|
||||
} else {
|
||||
box = PublisherBox(base: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
@@ -47,10 +103,10 @@ 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)
|
||||
box.receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +118,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,9 +140,9 @@ 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)
|
||||
base.receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
/// A type-erasing subscriber.
|
||||
///
|
||||
/// Use an `AnySubscriber` to wrap an existing subscriber whose details you don’t want
|
||||
/// to expose. You can also use `AnySubscriber` to create a custom subscriber by providing
|
||||
/// closures for `Subscriber`’s methods, rather than implementing `Subscriber` directly.
|
||||
/// Use an `AnySubscriber` to wrap an existing subscriber whose details you don’t want to
|
||||
/// expose. You can also use `AnySubscriber` to create a custom subscriber by providing
|
||||
/// closures for the methods defined in `Subscriber`, rather than implementing
|
||||
/// `Subscriber` directly.
|
||||
public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
@@ -44,6 +45,11 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Input == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
if let erased = subscriber as? AnySubscriber<Input, Failure> {
|
||||
self = erased
|
||||
return
|
||||
}
|
||||
|
||||
combineIdentifier = subscriber.combineIdentifier
|
||||
|
||||
box = AnySubscriberBox(subscriber)
|
||||
@@ -62,8 +68,8 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
|
||||
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
|
||||
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
|
||||
} else if let desccription = subscriber as? CustomStringConvertible {
|
||||
playgroundDescriptionThunk = { desccription.description }
|
||||
} else if let description = subscriber as? CustomStringConvertible {
|
||||
playgroundDescriptionThunk = { description.description }
|
||||
} else {
|
||||
let fixedDescription = String(describing: type(of: subscriber))
|
||||
playgroundDescriptionThunk = { fixedDescription }
|
||||
@@ -137,17 +143,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,53 +228,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)
|
||||
downstreamSubject = nil
|
||||
}
|
||||
|
||||
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,7 +5,7 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol indicating that an activity or action may be canceled.
|
||||
/// A protocol indicating that an activity or action supports cancellation.
|
||||
///
|
||||
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
|
||||
/// as timers, network access, or disk I/O.
|
||||
@@ -17,18 +17,18 @@ public protocol Cancellable {
|
||||
|
||||
extension Cancellable {
|
||||
|
||||
/// Stores this Cancellable in the specified collection.
|
||||
/// Parameters:
|
||||
/// - collection: The collection to store this Cancellable.
|
||||
/// Stores this cancellable instance in the specified collection.
|
||||
///
|
||||
/// - Parameter collection: The collection in which to store this `Cancellable`.
|
||||
public func store<Cancellables: RangeReplaceableCollection>(
|
||||
in collection: inout Cancellables
|
||||
) where Cancellables.Element == AnyCancellable {
|
||||
AnyCancellable(self).store(in: &collection)
|
||||
}
|
||||
|
||||
/// Stores this Cancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - collection: The set to store this Cancellable.
|
||||
/// Stores this cancellable instance in the specified set.
|
||||
///
|
||||
/// - Parameter set: The set in which to store this `Cancellable`.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
AnyCancellable(self).store(in: &set)
|
||||
}
|
||||
|
||||
@@ -5,17 +5,25 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
/// A type that defines methods for decoding.
|
||||
public protocol TopLevelDecoder {
|
||||
|
||||
/// The type this decoder accepts.
|
||||
associatedtype Input
|
||||
|
||||
/// Decodes an instance of the indicated type.
|
||||
func decode<DecodablyType: Decodable>(_ type: DecodablyType.Type,
|
||||
from: Input) throws -> DecodablyType
|
||||
}
|
||||
|
||||
/// A type that defines methods for encoding.
|
||||
public protocol TopLevelEncoder {
|
||||
|
||||
/// The type this encoder produces.
|
||||
associatedtype Output
|
||||
|
||||
/// Encodes an instance of the indicated type.
|
||||
///
|
||||
/// - Parameter value: The instance to encode.
|
||||
func encode<EncodableType: Encodable>(_ value: EncodableType) throws -> Output
|
||||
}
|
||||
|
||||
@@ -5,35 +5,41 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
#if WASI
|
||||
private var __identifier: UInt64 = 0
|
||||
|
||||
internal func __nextCombineIdentifier() -> UInt64 {
|
||||
defer { __identifier += 1 }
|
||||
return __identifier
|
||||
}
|
||||
#endif // WASI
|
||||
|
||||
/// A unique identifier for identifying publisher streams.
|
||||
///
|
||||
/// To conform to `CustomCombineIdentifierConvertible` in a
|
||||
/// `Subscription` or `Subject` that you implement as a structure, create an instance of
|
||||
/// `CombineIdentifier` as follows:
|
||||
///
|
||||
/// let combineIdentifier = CombineIdentifier()
|
||||
public struct CombineIdentifier: Hashable, CustomStringConvertible {
|
||||
|
||||
@usableFromInline
|
||||
internal static var _counter: UInt = 0
|
||||
private let rawValue: UInt64
|
||||
|
||||
@usableFromInline
|
||||
internal static var _counterLock = Lock(recursive: false)
|
||||
|
||||
@usableFromInline
|
||||
internal let _id: UInt
|
||||
|
||||
@inlinable
|
||||
/// Creates a unique Combine identifier.
|
||||
public init() {
|
||||
|
||||
var id: UInt = 0
|
||||
|
||||
CombineIdentifier._counterLock.do {
|
||||
id = CombineIdentifier._counter
|
||||
CombineIdentifier._counter += 1
|
||||
}
|
||||
|
||||
_id = id
|
||||
rawValue = __nextCombineIdentifier()
|
||||
}
|
||||
|
||||
/// Creates a Combine identifier, using the bit pattern of the provided object.
|
||||
public init(_ obj: AnyObject) {
|
||||
_id = UInt(bitPattern: ObjectIdentifier(obj))
|
||||
rawValue = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "0x\(String(_id, radix: 16))"
|
||||
return "0x\(String(rawValue, radix: 16))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// ConcurrencyHelpers.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 14.11.2022.
|
||||
//
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
#if (canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)) && swift(<5.7)
|
||||
/// A polyfill for pre-5.7 Swift versions.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
internal func withTaskCancellationHandler<T>( // swiftlint:disable:this generic_type_name
|
||||
operation: () async throws -> T,
|
||||
onCancel handler: @Sendable () -> Void
|
||||
) async rethrows -> T {
|
||||
return try await withTaskCancellationHandler(
|
||||
handler: handler,
|
||||
operation: operation
|
||||
)
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Future+Concurrency.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.08.2021.
|
||||
//
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)
|
||||
extension Future where Failure == Never {
|
||||
|
||||
/// The published value of the future, delivered asynchronously.
|
||||
///
|
||||
/// This property subscribes to the `Future` and delivers the value asynchronously
|
||||
/// when the `Future` publishes it. Use this property when you want to use
|
||||
/// the `async`-`await` syntax with a `Future`.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public var value: Output {
|
||||
get async {
|
||||
await ContinuationSubscriber.withUnsafeSubscription(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Future {
|
||||
|
||||
/// The published value of the future or an error, delivered asynchronously.
|
||||
///
|
||||
/// This property subscribes to the `Future` and delivers the value asynchronously
|
||||
/// when the `Future` publishes it. If the `Future` terminates with an error,
|
||||
/// the awaiting caller receives the error instead. Use this property when you want
|
||||
/// to the `async`-`await` syntax with a `Future` whose `Failure` type is not `Never`.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public var value: Output {
|
||||
get async throws {
|
||||
try await ContinuationSubscriber.withUnsafeThrowingSubscription(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
private final class ContinuationSubscriber<Input,
|
||||
UpstreamFailure: Error,
|
||||
ErrorOrNever: Error>
|
||||
: Subscriber
|
||||
{
|
||||
typealias Failure = UpstreamFailure
|
||||
|
||||
private var continuation: UnsafeContinuation<Input, ErrorOrNever>?
|
||||
private var subscription: Subscription?
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private init(_ continuation: UnsafeContinuation<Input, ErrorOrNever>) {
|
||||
self.continuation = continuation
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard self.subscription == nil else {
|
||||
assertionFailure("Unexpected state: received subscription twice")
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
subscription.request(.max(1))
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if let continuation = self.continuation.take() {
|
||||
lock.unlock()
|
||||
continuation.resume(returning: input)
|
||||
} else {
|
||||
assertionFailure("Unexpected state: already completed")
|
||||
lock.unlock()
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
completion.failure.map(handleFailure)
|
||||
}
|
||||
|
||||
private func handleFailure(_ error: Failure) {
|
||||
lock.lock()
|
||||
if let continuation = self.continuation.take() {
|
||||
lock.unlock()
|
||||
continuation.resume(throwing: error as! ErrorOrNever)
|
||||
} else {
|
||||
assertionFailure("Unexpected state: already completed")
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension ContinuationSubscriber where ErrorOrNever == Error {
|
||||
fileprivate static func withUnsafeThrowingSubscription<Upstream: Publisher>(
|
||||
_ upstream: Upstream
|
||||
) async throws -> Input
|
||||
where Upstream.Output == Input,
|
||||
Upstream.Failure == UpstreamFailure
|
||||
{
|
||||
try await withUnsafeThrowingContinuation { continuation in
|
||||
upstream.subscribe(ContinuationSubscriber(continuation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension ContinuationSubscriber where UpstreamFailure == Never, ErrorOrNever == Never {
|
||||
fileprivate static func withUnsafeSubscription<Upstream: Publisher>(
|
||||
_ upstream: Upstream
|
||||
) async -> Input
|
||||
where Upstream.Output == Input,
|
||||
Upstream.Failure == Never
|
||||
{
|
||||
await withUnsafeContinuation { continuation in
|
||||
upstream.subscribe(ContinuationSubscriber(continuation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,397 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publisher+Concurrency.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.08.2021.
|
||||
//
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// The elements produced by the publisher, as an asynchronous sequence.
|
||||
///
|
||||
/// This property provides an `AsyncPublisher`, which allows you to use
|
||||
/// the Swift `async`-`await` syntax to receive the publisher's elements.
|
||||
/// Because `AsyncPublisher` conforms to `AsyncSequence`, you iterate over its
|
||||
/// elements with a `for`-`await`-`in` loop, rather than attaching a subscriber.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public var values: AsyncPublisher<Self> {
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that exposes its elements as an asynchronous sequence.
|
||||
///
|
||||
/// `AsyncPublisher` conforms to `AsyncSequence`, which allows callers to receive
|
||||
/// values with the `for`-`await`-`in` syntax, rather than attaching a `Subscriber`.
|
||||
///
|
||||
/// Use the `values` property of the `Publisher` protocol to wrap an existing publisher
|
||||
/// with an instance of this type.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public struct AsyncPublisher<Upstream: Publisher>: AsyncSequence
|
||||
where Upstream.Failure == Never
|
||||
{
|
||||
|
||||
public typealias Element = Upstream.Output
|
||||
|
||||
/// The iterator that produces elements of the asynchronous publisher sequence.
|
||||
public struct Iterator: AsyncIteratorProtocol {
|
||||
|
||||
public typealias Element = Upstream.Output
|
||||
|
||||
fileprivate let inner: Inner
|
||||
|
||||
/// Produces the next element in the prefix sequence.
|
||||
///
|
||||
/// - Returns: The next published element, or `nil` if the publisher finishes
|
||||
/// normally.
|
||||
public mutating func next() async -> Element? {
|
||||
return await withTaskCancellationHandler(
|
||||
operation: { [inner] in await inner.next() },
|
||||
onCancel: { [inner] in inner.cancel() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of asynchronous iterator that produces elements of this
|
||||
/// asynchronous sequence.
|
||||
public typealias AsyncIterator = Iterator
|
||||
|
||||
private let publisher: Upstream
|
||||
|
||||
/// Creates a publisher that exposes elements received from an upstream publisher as
|
||||
/// a throwing asynchronous sequence.
|
||||
///
|
||||
/// - Parameter publisher: An upstream publisher. The asynchronous publisher converts
|
||||
/// elements received from this publisher into an asynchronous sequence.
|
||||
public init(_ publisher: Upstream) {
|
||||
self.publisher = publisher
|
||||
}
|
||||
|
||||
/// Creates the asynchronous iterator that produces elements of this asynchronous
|
||||
/// sequence.
|
||||
///
|
||||
/// - Returns: An instance of the `AsyncIterator` type used to produce elements of
|
||||
/// the asynchronous sequence.
|
||||
public func makeAsyncIterator() -> Iterator {
|
||||
let inner = Iterator.Inner()
|
||||
publisher.subscribe(inner)
|
||||
return Iterator(inner: inner)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension AsyncPublisher.Iterator {
|
||||
|
||||
fileprivate final class Inner: Subscriber, Cancellable {
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var pending: [UnsafeContinuation<Input?, Never>] = []
|
||||
private var state = State.awaitingSubscription
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
self.pendingDemand = .none
|
||||
lock.unlock()
|
||||
if pendingDemand != .none {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
return .none
|
||||
}
|
||||
precondition(!pending.isEmpty, "Received an output without requesting demand")
|
||||
let continuation = pending.removeFirst()
|
||||
lock.unlock()
|
||||
continuation.resume(returning: input)
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
state = .terminal
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let pending = self.pending.take()
|
||||
guard case .subscribed(let subscription) = state else {
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
pending.resumeAllWithNil()
|
||||
}
|
||||
|
||||
fileprivate func next() async -> Input? {
|
||||
return await withUnsafeContinuation { continuation in
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription:
|
||||
pending.append(continuation)
|
||||
pendingDemand += 1
|
||||
lock.unlock()
|
||||
case .subscribed(let subscription):
|
||||
pending.append(continuation)
|
||||
lock.unlock()
|
||||
subscription.request(.max(1))
|
||||
case .terminal:
|
||||
lock.unlock()
|
||||
continuation.resume(returning: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
extension Publisher {
|
||||
|
||||
/// The elements produced by the publisher, as a throwing asynchronous sequence.
|
||||
///
|
||||
/// This property provides an `AsyncThrowingPublisher`, which allows you to use
|
||||
/// the Swift `async`-`await` syntax to receive the publisher's elements.
|
||||
/// Because `AsyncPublisher` conforms to `AsyncSequence`, you iterate over its
|
||||
/// elements with a `for`-`await`-`in` loop, rather than attaching a subscriber.
|
||||
/// If the publisher terminates with an error, the awaiting caller receives the error
|
||||
/// as a `throw`.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public var values: AsyncThrowingPublisher<Self> {
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that exposes its elements as a throwing asynchronous sequence.
|
||||
///
|
||||
/// `AsyncThrowingPublisher` conforms to `AsyncSequence`, which allows callers to receive
|
||||
/// values with the `for`-`await`-`in` syntax, rather than attaching a `Subscriber`.
|
||||
/// If the upstream publisher terminates with an error, `AsyncThrowingPublisher` throws
|
||||
/// the error to the awaiting caller.
|
||||
///
|
||||
/// Use the `values` property of the `Publisher` protocol to wrap an existing publisher
|
||||
/// with an instance of this type.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public struct AsyncThrowingPublisher<Upstream: Publisher>: AsyncSequence
|
||||
{
|
||||
|
||||
public typealias Element = Upstream.Output
|
||||
|
||||
/// The iterator that produces elements of the asynchronous publisher sequence.
|
||||
public struct Iterator: AsyncIteratorProtocol {
|
||||
|
||||
public typealias Element = Upstream.Output
|
||||
|
||||
fileprivate let inner: Inner
|
||||
|
||||
/// Produces the next element in the prefix sequence.
|
||||
///
|
||||
/// - Returns: The next published element, or `nil` if the publisher finishes
|
||||
/// normally.
|
||||
/// If the publisher terminates with an error, the call point receives
|
||||
/// the error as a `throw`.
|
||||
public mutating func next() async throws -> Element? {
|
||||
return try await withTaskCancellationHandler(
|
||||
operation: { [inner] in try await inner.next() },
|
||||
onCancel: { [inner] in inner.cancel() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of asynchronous iterator that produces elements of this
|
||||
/// asynchronous sequence.
|
||||
public typealias AsyncIterator = Iterator
|
||||
|
||||
private let publisher: Upstream
|
||||
|
||||
/// Creates a publisher that exposes elements received from an upstream publisher as
|
||||
/// an asynchronous sequence.
|
||||
///
|
||||
/// - Parameter publisher: An upstream publisher. The asynchronous publisher converts
|
||||
/// elements received from this publisher into an asynchronous sequence.
|
||||
public init(_ publisher: Upstream) {
|
||||
self.publisher = publisher
|
||||
}
|
||||
|
||||
/// Creates the asynchronous iterator that produces elements of this asynchronous
|
||||
/// sequence.
|
||||
///
|
||||
/// - Returns: An instance of the `AsyncIterator` type used to produce elements of
|
||||
/// the asynchronous sequence.
|
||||
public func makeAsyncIterator() -> Iterator {
|
||||
let inner = Iterator.Inner()
|
||||
publisher.subscribe(inner)
|
||||
return Iterator(inner: inner)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension AsyncThrowingPublisher.Iterator {
|
||||
|
||||
fileprivate final class Inner: Subscriber, Cancellable {
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case terminal(Error?)
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var pending: [UnsafeContinuation<Input?, Error>] = []
|
||||
private var state = State.awaitingSubscription
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
self.pendingDemand = .none
|
||||
lock.unlock()
|
||||
if pendingDemand != .none {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
return .none
|
||||
}
|
||||
precondition(!pending.isEmpty, "Received an output without requesting demand")
|
||||
let continuation = pending.removeFirst()
|
||||
lock.unlock()
|
||||
continuation.resume(returning: input)
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription, .subscribed:
|
||||
if let continuation = pending.first {
|
||||
state = .terminal(nil)
|
||||
let remaining = pending.take().dropFirst()
|
||||
lock.unlock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
continuation.resume(returning: nil)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
remaining.resumeAllWithNil()
|
||||
} else {
|
||||
state = .terminal(completion.failure)
|
||||
lock.unlock()
|
||||
}
|
||||
case .terminal:
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let pending = self.pending.take()
|
||||
guard case .subscribed(let subscription) = state else {
|
||||
state = .terminal(nil)
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
return
|
||||
}
|
||||
state = .terminal(nil)
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
pending.resumeAllWithNil()
|
||||
}
|
||||
|
||||
fileprivate func next() async throws -> Input? {
|
||||
return try await withUnsafeThrowingContinuation { continuation in
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription:
|
||||
pending.append(continuation)
|
||||
pendingDemand += 1
|
||||
lock.unlock()
|
||||
case .subscribed(let subscription):
|
||||
pending.append(continuation)
|
||||
lock.unlock()
|
||||
subscription.request(.max(1))
|
||||
case .terminal(nil):
|
||||
lock.unlock()
|
||||
continuation.resume(returning: nil)
|
||||
case .terminal(let error?):
|
||||
state = .terminal(nil)
|
||||
lock.unlock()
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension Sequence {
|
||||
fileprivate func resumeAllWithNil<Output, Failure: Error>()
|
||||
where Element == UnsafeContinuation<Output?, Failure>
|
||||
{
|
||||
for continuation in self {
|
||||
continuation.resume(returning: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,249 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publisher+Concurrency.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.08.2021.
|
||||
//
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)
|
||||
%{
|
||||
instantiations = [('AsyncPublisher', False), ('AsyncThrowingPublisher', True)]
|
||||
}%
|
||||
% for instantiation, throwing in instantiations:
|
||||
extension Publisher ${'' if throwing else 'where Failure == Never '}{
|
||||
|
||||
/// The elements produced by the publisher, as ${'a throwing' if throwing else 'an'} asynchronous sequence.
|
||||
///
|
||||
/// This property provides an `${instantiation}`, which allows you to use
|
||||
/// the Swift `async`-`await` syntax to receive the publisher's elements.
|
||||
/// Because `AsyncPublisher` conforms to `AsyncSequence`, you iterate over its
|
||||
/// elements with a `for`-`await`-`in` loop, rather than attaching a subscriber.
|
||||
% if throwing:
|
||||
/// If the publisher terminates with an error, the awaiting caller receives the error
|
||||
/// as a `throw`.
|
||||
% end
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public var values: ${instantiation}<Self> {
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that exposes its elements as ${'a throwing' if throwing else 'an'} asynchronous sequence.
|
||||
///
|
||||
/// `${instantiation}` conforms to `AsyncSequence`, which allows callers to receive
|
||||
/// values with the `for`-`await`-`in` syntax, rather than attaching a `Subscriber`.
|
||||
% if throwing:
|
||||
/// If the upstream publisher terminates with an error, `${instantiation}` throws
|
||||
/// the error to the awaiting caller.
|
||||
% end
|
||||
///
|
||||
/// Use the `values` property of the `Publisher` protocol to wrap an existing publisher
|
||||
/// with an instance of this type.
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public struct ${instantiation}<Upstream: Publisher>: AsyncSequence
|
||||
% if not throwing:
|
||||
where Upstream.Failure == Never
|
||||
% end
|
||||
{
|
||||
|
||||
public typealias Element = Upstream.Output
|
||||
|
||||
/// The iterator that produces elements of the asynchronous publisher sequence.
|
||||
public struct Iterator: AsyncIteratorProtocol {
|
||||
|
||||
public typealias Element = Upstream.Output
|
||||
|
||||
fileprivate let inner: Inner
|
||||
|
||||
/// Produces the next element in the prefix sequence.
|
||||
///
|
||||
/// - Returns: The next published element, or `nil` if the publisher finishes
|
||||
/// normally.
|
||||
% if throwing:
|
||||
/// If the publisher terminates with an error, the call point receives
|
||||
/// the error as a `throw`.
|
||||
% end
|
||||
public mutating func next() async ${'throws ' if throwing else ''}-> Element? {
|
||||
return ${'try ' if throwing else ''}await withTaskCancellationHandler(
|
||||
operation: { [inner] in ${'try ' if throwing else ''}await inner.next() },
|
||||
onCancel: { [inner] in inner.cancel() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of asynchronous iterator that produces elements of this
|
||||
/// asynchronous sequence.
|
||||
public typealias AsyncIterator = Iterator
|
||||
|
||||
private let publisher: Upstream
|
||||
|
||||
/// Creates a publisher that exposes elements received from an upstream publisher as
|
||||
% if throwing:
|
||||
/// an asynchronous sequence.
|
||||
% else:
|
||||
/// a throwing asynchronous sequence.
|
||||
% end
|
||||
///
|
||||
/// - Parameter publisher: An upstream publisher. The asynchronous publisher converts
|
||||
/// elements received from this publisher into an asynchronous sequence.
|
||||
public init(_ publisher: Upstream) {
|
||||
self.publisher = publisher
|
||||
}
|
||||
|
||||
/// Creates the asynchronous iterator that produces elements of this asynchronous
|
||||
/// sequence.
|
||||
///
|
||||
/// - Returns: An instance of the `AsyncIterator` type used to produce elements of
|
||||
/// the asynchronous sequence.
|
||||
public func makeAsyncIterator() -> Iterator {
|
||||
let inner = Iterator.Inner()
|
||||
publisher.subscribe(inner)
|
||||
return Iterator(inner: inner)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension ${instantiation}.Iterator {
|
||||
|
||||
fileprivate final class Inner: Subscriber, Cancellable {
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case terminal${'(Error?)' if throwing else ''}
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var pending: [UnsafeContinuation<Input?, ${'Error' if throwing else 'Never'}>] = []
|
||||
private var state = State.awaitingSubscription
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
self.pendingDemand = .none
|
||||
lock.unlock()
|
||||
if pendingDemand != .none {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
return .none
|
||||
}
|
||||
precondition(!pending.isEmpty, "Received an output without requesting demand")
|
||||
let continuation = pending.removeFirst()
|
||||
lock.unlock()
|
||||
continuation.resume(returning: input)
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
% if throwing:
|
||||
switch state {
|
||||
case .awaitingSubscription, .subscribed:
|
||||
if let continuation = pending.first {
|
||||
state = .terminal(nil)
|
||||
let remaining = pending.take().dropFirst()
|
||||
lock.unlock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
continuation.resume(returning: nil)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
remaining.resumeAllWithNil()
|
||||
} else {
|
||||
state = .terminal(completion.failure)
|
||||
lock.unlock()
|
||||
}
|
||||
case .terminal:
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
}
|
||||
% else:
|
||||
state = .terminal
|
||||
let pending = self.pending.take()
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
% end
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let pending = self.pending.take()
|
||||
guard case .subscribed(let subscription) = state else {
|
||||
state = .terminal${'(nil)' if throwing else ''}
|
||||
lock.unlock()
|
||||
pending.resumeAllWithNil()
|
||||
return
|
||||
}
|
||||
state = .terminal${'(nil)' if throwing else ''}
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
pending.resumeAllWithNil()
|
||||
}
|
||||
|
||||
fileprivate func next() async ${'throws ' if throwing else ''}-> Input? {
|
||||
return ${'try ' if throwing else ''}await withUnsafe${'Throwing' if throwing else ''}Continuation { continuation in
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .awaitingSubscription:
|
||||
pending.append(continuation)
|
||||
pendingDemand += 1
|
||||
lock.unlock()
|
||||
case .subscribed(let subscription):
|
||||
pending.append(continuation)
|
||||
lock.unlock()
|
||||
subscription.request(.max(1))
|
||||
case .terminal${'(nil)' if throwing else ''}:
|
||||
lock.unlock()
|
||||
continuation.resume(returning: nil)
|
||||
% if throwing:
|
||||
case .terminal(let error?):
|
||||
state = .terminal(nil)
|
||||
lock.unlock()
|
||||
continuation.resume(throwing: error)
|
||||
% end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
% end
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
extension Sequence {
|
||||
fileprivate func resumeAllWithNil<Output, Failure: Error>()
|
||||
where Element == UnsafeContinuation<Output?, Failure>
|
||||
{
|
||||
for continuation in self {
|
||||
continuation.resume(returning: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// ConnectablePublisher.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 14.06.2019.
|
||||
//
|
||||
|
||||
/// A publisher that provides an explicit means of connecting and canceling publication.
|
||||
///
|
||||
/// Use `makeConnectable()` to create a `ConnectablePublisher` from any publisher whose
|
||||
/// failure type is `Never`.
|
||||
public protocol ConnectablePublisher: Publisher {
|
||||
|
||||
/// Connects to the publisher and returns a `Cancellable` instance with which
|
||||
/// to cancel publishing.
|
||||
///
|
||||
/// - Returns: A `Cancellable` instance that can be used to cancel publishing.
|
||||
func connect() -> Cancellable
|
||||
}
|
||||
@@ -7,28 +7,37 @@
|
||||
|
||||
/// A subject that wraps a single value and publishes a new element whenever the value
|
||||
/// changes.
|
||||
///
|
||||
/// Unlike `PassthroughSubject`, `CurrentValueSubject` maintains a buffer of the most
|
||||
/// recently published element.
|
||||
///
|
||||
/// Calling `send(_:)` on a `CurrentValueSubject` also updates the current value, making
|
||||
/// it equivalent to updating the `value` directly.
|
||||
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _lock = Lock(recursive: true)
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
private var active = true
|
||||
|
||||
private var _value: Output
|
||||
private var completion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
private var currentValue: Output
|
||||
|
||||
internal var hasAnyDownstreamDemand = false
|
||||
private var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
/// The value wrapped by this subject, published as a new element whenever it changes.
|
||||
public var value: Output {
|
||||
get {
|
||||
return _value
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return currentValue
|
||||
}
|
||||
set {
|
||||
send(newValue)
|
||||
lock.lock()
|
||||
currentValue = newValue
|
||||
sendValueAndConsumeLock(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,121 +45,208 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
///
|
||||
/// - Parameter value: The initial value to publish.
|
||||
public init(_ value: Output) {
|
||||
self._value = value
|
||||
self.currentValue = value
|
||||
}
|
||||
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
lock.lock()
|
||||
upstreamSubscriptions.append(subscription)
|
||||
lock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Output == Subscriber.Input, Failure == Subscriber.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
_lock.do {
|
||||
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
lock.lock()
|
||||
if active {
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
} else {
|
||||
let completion = self.completion!
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
_lock.do {
|
||||
_value = input
|
||||
for subscription in _subscriptions where !subscription.isCompleted {
|
||||
if subscription._demand > 0 {
|
||||
subscription._offer(input)
|
||||
subscription._demand -= 1
|
||||
} else {
|
||||
subscription._delivered = false
|
||||
}
|
||||
}
|
||||
lock.lock()
|
||||
sendValueAndConsumeLock(input)
|
||||
}
|
||||
|
||||
private func sendValueAndConsumeLock(_ newValue: Output) {
|
||||
#if DEBUG
|
||||
lock.assertOwner()
|
||||
#endif
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
currentValue = newValue
|
||||
let downstreams = self.downstreams
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.offer(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
_completion = completion
|
||||
_lock.do {
|
||||
for subscriber in _subscriptions {
|
||||
subscriber._receive(completion: completion)
|
||||
}
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
active = false
|
||||
self.completion = completion
|
||||
let downstreams = self.downstreams.take()
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.finish(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentValueSubject {
|
||||
|
||||
fileprivate class Conduit: Subscription {
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
|
||||
fileprivate var _parent: CurrentValueSubject?
|
||||
fileprivate var parent: CurrentValueSubject?
|
||||
|
||||
fileprivate var _downstream: AnySubscriber<Output, Failure>?
|
||||
fileprivate var downstream: Downstream?
|
||||
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
|
||||
/// Whethere we satisfied the demand
|
||||
fileprivate var _delivered = false
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
var isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate func _offer(_ value: Output) {
|
||||
let newDemand = _downstream?.receive(value) ?? .none
|
||||
_demand += newDemand
|
||||
_delivered = true
|
||||
}
|
||||
private var deliveredCurrentValue = false
|
||||
|
||||
fileprivate init(parent: CurrentValueSubject,
|
||||
downstream: AnySubscriber<Output, Failure>) {
|
||||
_parent = parent
|
||||
_downstream = downstream
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
lock.lock()
|
||||
guard demand > 0, let downstream = self.downstream else {
|
||||
deliveredCurrentValue = false
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
demand -= 1
|
||||
deliveredCurrentValue = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(output)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
precondition(demand > 0)
|
||||
_parent?._lock.do {
|
||||
if !_delivered, let value = _parent?.value {
|
||||
_offer(value)
|
||||
_demand += demand
|
||||
_demand -= 1
|
||||
} else {
|
||||
_demand = demand
|
||||
}
|
||||
_parent?.hasAnyDownstreamDemand = true
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_parent = nil
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if deliveredCurrentValue {
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Hasn't yet delivered the current value
|
||||
|
||||
self.demand += demand
|
||||
deliveredCurrentValue = true
|
||||
if let currentValue = self.parent?.value {
|
||||
self.demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(currentValue)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
self.demand += newDemand
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if downstream.take() == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
var description: String { return "CurrentValueSubject" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("demand", demand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentValueSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "CurrentValueSubject" }
|
||||
}
|
||||
|
||||
@@ -5,8 +5,18 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol for uniquely identifying publisher streams.
|
||||
///
|
||||
/// If you create a custom `Subscription` or `Subscriber` type, implement this protocol
|
||||
/// so that development tools can uniquely identify publisher chains in your app.
|
||||
/// If your type is a class, OpenCombine provides an implementation of `combineIdentifier`
|
||||
/// for you.
|
||||
/// If your type is a structure, set up the identifier as follows:
|
||||
///
|
||||
/// let combineIdentifier = CombineIdentifier()
|
||||
public protocol CustomCombineIdentifierConvertible {
|
||||
|
||||
/// A unique identifier for identifying publisher streams.
|
||||
var combineIdentifier: CombineIdentifier { get }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
//
|
||||
// Future.swift
|
||||
//
|
||||
//
|
||||
// Created by Max Desiatov on 24/11/2019.
|
||||
//
|
||||
|
||||
/// A publisher that eventually produces a single value and then finishes or fails.
|
||||
public final class Future<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// A type that represents a closure to invoke in the future, when an element or error
|
||||
/// is available.
|
||||
///
|
||||
/// The promise closure receives one parameter: a `Result` that contains either
|
||||
/// a single element published by a `Future`, or an error.
|
||||
public typealias Promise = (Result<Output, Failure>) -> Void
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
|
||||
private var result: Result<Output, Failure>?
|
||||
|
||||
/// Creates a publisher that invokes a promise closure when the publisher emits
|
||||
/// an element.
|
||||
///
|
||||
/// - Parameter attemptToFulfill: A `Promise` that the publisher invokes when
|
||||
/// the publisher emits an element or terminates with an error.
|
||||
public init(
|
||||
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
|
||||
) {
|
||||
attemptToFulfill(self.promise)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
private func promise(_ result: Result<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard self.result == nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.result = result
|
||||
let downstreams = self.downstreams.take()
|
||||
lock.unlock()
|
||||
switch result {
|
||||
case .success(let output):
|
||||
downstreams.forEach { $0.offer(output) }
|
||||
case .failure(let error):
|
||||
downstreams.forEach { $0.finish(completion: .failure(error)) }
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
lock.lock()
|
||||
if let result = self.result {
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
conduit.fulfill(result)
|
||||
} else {
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
}
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension Future {
|
||||
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
private enum State {
|
||||
case active(Downstream, hasAnyDemand: Bool)
|
||||
case terminal
|
||||
|
||||
var downstream: Downstream? {
|
||||
switch self {
|
||||
case .active(let downstream, hasAnyDemand: _):
|
||||
return downstream
|
||||
case .terminal:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var hasAnyDemand: Bool {
|
||||
switch self {
|
||||
case .active(_, let hasAnyDemand):
|
||||
return hasAnyDemand
|
||||
case .terminal:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var parent: Future?
|
||||
|
||||
private var state: State
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(parent: Future, downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.state = .active(downstream, hasAnyDemand: false)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
fileprivate func lockedFulfill(downstream: Downstream,
|
||||
result: Result<Output, Failure>) {
|
||||
switch result {
|
||||
case .success(let output):
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func fulfill(_ result: Result<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard case let .active(downstream, hasAnyDemand) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if case .success = result, !hasAnyDemand {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
lockedFulfill(downstream: downstream, result: result)
|
||||
downstreamLock.unlock()
|
||||
lock.lock()
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
fulfill(.success(output))
|
||||
}
|
||||
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
assertionFailure("unreachable")
|
||||
case .failure(let error):
|
||||
fulfill(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard case .active(let downstream, hasAnyDemand: _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .active(downstream, hasAnyDemand: true)
|
||||
|
||||
if let parent = parent, let result = parent.result {
|
||||
// If the promise is already resolved, send the result downstream
|
||||
// immediately
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
lockedFulfill(downstream: downstream, result: result)
|
||||
downstreamLock.unlock()
|
||||
parent.disassociate(self)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .active:
|
||||
state = .terminal
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
case .terminal:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "Future" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", state.downstream as Any),
|
||||
("hasAnyDemand", state.hasAnyDemand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// RootProtocols.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
#if compiler(>=5.7)
|
||||
/// Declares that a type can transmit a sequence of values over time.
|
||||
///
|
||||
/// A publisher delivers elements to one or more `Subscriber` instances.
|
||||
/// The subscriber’s `Input` and `Failure` associated types must match the `Output` and
|
||||
/// `Failure` types declared by the publisher.
|
||||
/// The publisher implements the `receive(subscriber:)`method to accept a subscriber.
|
||||
///
|
||||
/// After this, the publisher can call the following methods on the subscriber:
|
||||
/// - `receive(subscription:)`: Acknowledges the subscribe request and returns
|
||||
/// a `Subscription` instance. The subscriber uses the subscription to demand elements
|
||||
/// from the publisher and can use it to cancel publishing.
|
||||
/// - `receive(_:)`: Delivers one element from the publisher to the subscriber.
|
||||
/// - `receive(completion:)`: Informs the subscriber that publishing has ended,
|
||||
/// either normally or with an error.
|
||||
///
|
||||
/// Every `Publisher` must adhere to this contract for downstream subscribers to function
|
||||
/// correctly.
|
||||
///
|
||||
/// Extensions on `Publisher` define a wide variety of _operators_ that you compose to
|
||||
/// create sophisticated event-processing chains.
|
||||
/// Each operator returns a type that implements the `Publisher` protocol
|
||||
/// Most of these types exist as extensions on the `Publishers` enumeration.
|
||||
/// For example, the `map(_:)` operator returns an instance of `Publishers.Map`.
|
||||
///
|
||||
/// # Creating Your Own Publishers
|
||||
///
|
||||
/// Rather than implementing the `Publisher` protocol yourself, you can create your own
|
||||
/// publisher by using one of several types provided by the OpenCombine framework:
|
||||
///
|
||||
/// - Use a concrete subclass of `Subject`, such as `PassthroughSubject`, to publish
|
||||
/// values on-demand by calling its `send(_:)` method.
|
||||
/// - Use a `CurrentValueSubject` to publish whenever you update the subject’s underlying
|
||||
/// value.
|
||||
/// - Add the `@Published` annotation to a property of one of your own types. In doing so,
|
||||
/// the property gains a publisher that emits an event whenever the property’s value
|
||||
/// changes. See the `Published` type for an example of this approach.
|
||||
public protocol Publisher<Output, Failure> {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
associatedtype Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
associatedtype Failure: Error
|
||||
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// Always call this function instead of `receive(subscriber:)`.
|
||||
/// Adopters of `Publisher` must implement `receive(subscriber:)`. The implementation
|
||||
/// of `subscribe(_:)` provided by `Publisher` calls through to
|
||||
/// `receive(subscriber:)`.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to attach to this publisher. After
|
||||
/// attaching, the subscriber can start to receive values.
|
||||
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
}
|
||||
|
||||
/// A publisher that exposes a method for outside callers to publish elements.
|
||||
///
|
||||
/// A subject is a publisher that you can use to ”inject” values into a stream, by calling
|
||||
/// its `send()` method. This can be useful for adapting existing imperative code to the
|
||||
/// Combine model.
|
||||
public protocol Subject<Output, Failure>: AnyObject, Publisher {
|
||||
|
||||
/// Sends a value to the subscriber.
|
||||
///
|
||||
/// - Parameter value: The value to send.
|
||||
func send(_ value: Output)
|
||||
|
||||
/// Sends a completion signal to the subscriber.
|
||||
///
|
||||
/// - Parameter completion: A `Completion` instance which indicates whether publishing
|
||||
/// has finished normally or failed with an error.
|
||||
func send(completion: Subscribers.Completion<Failure>)
|
||||
|
||||
/// Sends a subscription to the subscriber.
|
||||
///
|
||||
/// This call provides the `Subject` an opportunity to establish demand for any new
|
||||
/// upstream subscriptions.
|
||||
///
|
||||
/// - Parameter subscription: The subscription instance through which the subscriber
|
||||
/// can request elements.
|
||||
func send(subscription: Subscription)
|
||||
}
|
||||
|
||||
/// A publisher that provides an explicit means of connecting and canceling publication.
|
||||
///
|
||||
/// Use a `ConnectablePublisher` when you need to perform additional configuration or
|
||||
/// setup prior to producing any elements.
|
||||
///
|
||||
/// This publisher doesn’t produce any elements until you call its `connect()` method.
|
||||
///
|
||||
/// Use `makeConnectable()` to create a `ConnectablePublisher` from any publisher whose
|
||||
/// failure type is `Never`.
|
||||
public protocol ConnectablePublisher<Output, Failure>: Publisher {
|
||||
|
||||
/// Connects to the publisher, allowing it to produce elements, and returns
|
||||
/// an instance with which to cancel publishing.
|
||||
///
|
||||
/// - Returns: A `Cancellable` instance that you use to cancel publishing.
|
||||
func connect() -> Cancellable
|
||||
}
|
||||
|
||||
/// A protocol that declares a type that can receive input from a publisher.
|
||||
///
|
||||
/// A `Subscriber` instance receives a stream of elements from a `Publisher`, along with
|
||||
/// life cycle events describing changes to their relationship. A given subscriber’s
|
||||
/// `Input` and `Failure` associated types must match the `Output` and `Failure` of its
|
||||
/// corresponding publisher.
|
||||
///
|
||||
/// You connect a subscriber to a publisher by calling the publisher’s `subscribe(_:)`
|
||||
/// method. After making this call, the publisher invokes the subscriber’s
|
||||
/// `receive(subscription:)` method. This gives the subscriber a `Subscription` instance,
|
||||
/// which it uses to demand elements from the publisher, and to optionally cancel
|
||||
/// the subscription. After the subscriber makes an initial demand, the publisher calls
|
||||
/// `receive(_:)`, possibly asynchronously, to deliver newly-published elements.
|
||||
/// If the publisher stops publishing, it calls `receive(completion:)`, using a parameter
|
||||
/// of type `Subscribers.Completion` to indicate whether publishing completes normally or
|
||||
/// with an error.
|
||||
///
|
||||
/// OpenCombine provides the following subscribers as operators on the `Publisher` type:
|
||||
///
|
||||
/// - `sink(receiveCompletion:receiveValue:)` executes arbitrary closures when
|
||||
/// it receives a completion signal and each time it receives a new element.
|
||||
/// - `assign(to:on:)` writes each newly-received value to a property identified by
|
||||
/// a key path on a given instance.
|
||||
public protocol Subscriber<Input, Failure>: CustomCombineIdentifierConvertible {
|
||||
|
||||
/// The kind of values this subscriber receives.
|
||||
associatedtype Input
|
||||
|
||||
/// The kind of errors this subscriber might receive.
|
||||
///
|
||||
/// Use `Never` if this `Subscriber` cannot receive errors.
|
||||
associatedtype Failure: Error
|
||||
|
||||
/// Tells the subscriber that it has successfully subscribed to the publisher and may
|
||||
/// request items.
|
||||
///
|
||||
/// Use the received `Subscription` to request items from the publisher.
|
||||
/// - Parameter subscription: A subscription that represents the connection between
|
||||
/// publisher and subscriber.
|
||||
func receive(subscription: Subscription)
|
||||
|
||||
/// Tells the subscriber that the publisher has produced an element.
|
||||
///
|
||||
/// - Parameter input: The published element.
|
||||
/// - Returns: A `Subscribers.Demand` instance indicating how many more elements
|
||||
/// the subscriber expects to receive.
|
||||
func receive(_ input: Input) -> Subscribers.Demand
|
||||
|
||||
/// Tells the subscriber that the publisher has completed publishing, either normally
|
||||
/// or with an error.
|
||||
///
|
||||
/// - Parameter completion: A `Subscribers.Completion` case indicating whether
|
||||
/// publishing completed normally or with an error.
|
||||
func receive(completion: Subscribers.Completion<Failure>)
|
||||
}
|
||||
|
||||
/// A protocol that defines when and how to execute a closure.
|
||||
///
|
||||
/// You can use a scheduler to execute code as soon as possible, or after a future date.
|
||||
/// Individual scheduler implementations use whatever time-keeping system makes sense
|
||||
/// for them. Schedulers express this as their `SchedulerTimeType`. Since this type
|
||||
/// conforms to `SchedulerTimeIntervalConvertible`, you can always express these times
|
||||
/// with the convenience functions like `.milliseconds(500)`. Schedulers can accept
|
||||
/// options to control how they execute the actions passed to them. These options may
|
||||
/// control factors like which threads or dispatch queues execute the actions.
|
||||
public protocol Scheduler<SchedulerTimeType> {
|
||||
|
||||
/// Describes an instant in time for this scheduler.
|
||||
associatedtype SchedulerTimeType: Strideable
|
||||
where SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible
|
||||
|
||||
/// A type that defines options accepted by the scheduler.
|
||||
///
|
||||
/// This type is freely definable by each `Scheduler`. Typically, operations that
|
||||
/// take a `Scheduler` parameter will also take `SchedulerOptions`.
|
||||
associatedtype SchedulerOptions
|
||||
|
||||
/// This scheduler’s definition of the current moment in time.
|
||||
var now: SchedulerTimeType { get }
|
||||
|
||||
/// The minimum tolerance allowed by the scheduler.
|
||||
var minimumTolerance: SchedulerTimeType.Stride { get }
|
||||
|
||||
/// Performs the action at the next possible opportunity.
|
||||
func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void)
|
||||
|
||||
/// Performs the action at some time after the specified date.
|
||||
func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void)
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
/// frequency, optionally taking into account tolerance if possible.
|
||||
func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable
|
||||
}
|
||||
#else
|
||||
/// Declares that a type can transmit a sequence of values over time.
|
||||
///
|
||||
/// A publisher delivers elements to one or more `Subscriber` instances.
|
||||
/// The subscriber’s `Input` and `Failure` associated types must match the `Output` and
|
||||
/// `Failure` types declared by the publisher.
|
||||
/// The publisher implements the `receive(subscriber:)`method to accept a subscriber.
|
||||
///
|
||||
/// After this, the publisher can call the following methods on the subscriber:
|
||||
/// - `receive(subscription:)`: Acknowledges the subscribe request and returns
|
||||
/// a `Subscription` instance. The subscriber uses the subscription to demand elements
|
||||
/// from the publisher and can use it to cancel publishing.
|
||||
/// - `receive(_:)`: Delivers one element from the publisher to the subscriber.
|
||||
/// - `receive(completion:)`: Informs the subscriber that publishing has ended,
|
||||
/// either normally or with an error.
|
||||
///
|
||||
/// Every `Publisher` must adhere to this contract for downstream subscribers to function
|
||||
/// correctly.
|
||||
///
|
||||
/// Extensions on `Publisher` define a wide variety of _operators_ that you compose to
|
||||
/// create sophisticated event-processing chains.
|
||||
/// Each operator returns a type that implements the `Publisher` protocol
|
||||
/// Most of these types exist as extensions on the `Publishers` enumeration.
|
||||
/// For example, the `map(_:)` operator returns an instance of `Publishers.Map`.
|
||||
///
|
||||
/// # Creating Your Own Publishers
|
||||
///
|
||||
/// Rather than implementing the `Publisher` protocol yourself, you can create your own
|
||||
/// publisher by using one of several types provided by the OpenCombine framework:
|
||||
///
|
||||
/// - Use a concrete subclass of `Subject`, such as `PassthroughSubject`, to publish
|
||||
/// values on-demand by calling its `send(_:)` method.
|
||||
/// - Use a `CurrentValueSubject` to publish whenever you update the subject’s underlying
|
||||
/// value.
|
||||
/// - Add the `@Published` annotation to a property of one of your own types. In doing so,
|
||||
/// the property gains a publisher that emits an event whenever the property’s value
|
||||
/// changes. See the `Published` type for an example of this approach.
|
||||
public protocol Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
associatedtype Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
associatedtype Failure: Error
|
||||
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// Always call this function instead of `receive(subscriber:)`.
|
||||
/// Adopters of `Publisher` must implement `receive(subscriber:)`. The implementation
|
||||
/// of `subscribe(_:)` provided by `Publisher` calls through to
|
||||
/// `receive(subscriber:)`.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to attach to this publisher. After
|
||||
/// attaching, the subscriber can start to receive values.
|
||||
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
}
|
||||
|
||||
/// A publisher that exposes a method for outside callers to publish elements.
|
||||
///
|
||||
/// A subject is a publisher that you can use to ”inject” values into a stream, by calling
|
||||
/// its `send()` method. This can be useful for adapting existing imperative code to the
|
||||
/// Combine model.
|
||||
public protocol Subject: AnyObject, Publisher {
|
||||
|
||||
/// Sends a value to the subscriber.
|
||||
///
|
||||
/// - Parameter value: The value to send.
|
||||
func send(_ value: Output)
|
||||
|
||||
/// Sends a completion signal to the subscriber.
|
||||
///
|
||||
/// - Parameter completion: A `Completion` instance which indicates whether publishing
|
||||
/// has finished normally or failed with an error.
|
||||
func send(completion: Subscribers.Completion<Failure>)
|
||||
|
||||
/// Sends a subscription to the subscriber.
|
||||
///
|
||||
/// This call provides the `Subject` an opportunity to establish demand for any new
|
||||
/// upstream subscriptions.
|
||||
///
|
||||
/// - Parameter subscription: The subscription instance through which the subscriber
|
||||
/// can request elements.
|
||||
func send(subscription: Subscription)
|
||||
}
|
||||
|
||||
/// A publisher that provides an explicit means of connecting and canceling publication.
|
||||
///
|
||||
/// Use a `ConnectablePublisher` when you need to perform additional configuration or
|
||||
/// setup prior to producing any elements.
|
||||
///
|
||||
/// This publisher doesn’t produce any elements until you call its `connect()` method.
|
||||
///
|
||||
/// Use `makeConnectable()` to create a `ConnectablePublisher` from any publisher whose
|
||||
/// failure type is `Never`.
|
||||
public protocol ConnectablePublisher: Publisher {
|
||||
|
||||
/// Connects to the publisher, allowing it to produce elements, and returns
|
||||
/// an instance with which to cancel publishing.
|
||||
///
|
||||
/// - Returns: A `Cancellable` instance that you use to cancel publishing.
|
||||
func connect() -> Cancellable
|
||||
}
|
||||
|
||||
/// A protocol that declares a type that can receive input from a publisher.
|
||||
///
|
||||
/// A `Subscriber` instance receives a stream of elements from a `Publisher`, along with
|
||||
/// life cycle events describing changes to their relationship. A given subscriber’s
|
||||
/// `Input` and `Failure` associated types must match the `Output` and `Failure` of its
|
||||
/// corresponding publisher.
|
||||
///
|
||||
/// You connect a subscriber to a publisher by calling the publisher’s `subscribe(_:)`
|
||||
/// method. After making this call, the publisher invokes the subscriber’s
|
||||
/// `receive(subscription:)` method. This gives the subscriber a `Subscription` instance,
|
||||
/// which it uses to demand elements from the publisher, and to optionally cancel
|
||||
/// the subscription. After the subscriber makes an initial demand, the publisher calls
|
||||
/// `receive(_:)`, possibly asynchronously, to deliver newly-published elements.
|
||||
/// If the publisher stops publishing, it calls `receive(completion:)`, using a parameter
|
||||
/// of type `Subscribers.Completion` to indicate whether publishing completes normally or
|
||||
/// with an error.
|
||||
///
|
||||
/// OpenCombine provides the following subscribers as operators on the `Publisher` type:
|
||||
///
|
||||
/// - `sink(receiveCompletion:receiveValue:)` executes arbitrary closures when
|
||||
/// it receives a completion signal and each time it receives a new element.
|
||||
/// - `assign(to:on:)` writes each newly-received value to a property identified by
|
||||
/// a key path on a given instance.
|
||||
public protocol Subscriber: CustomCombineIdentifierConvertible {
|
||||
|
||||
/// The kind of values this subscriber receives.
|
||||
associatedtype Input
|
||||
|
||||
/// The kind of errors this subscriber might receive.
|
||||
///
|
||||
/// Use `Never` if this `Subscriber` cannot receive errors.
|
||||
associatedtype Failure: Error
|
||||
|
||||
/// Tells the subscriber that it has successfully subscribed to the publisher and may
|
||||
/// request items.
|
||||
///
|
||||
/// Use the received `Subscription` to request items from the publisher.
|
||||
/// - Parameter subscription: A subscription that represents the connection between
|
||||
/// publisher and subscriber.
|
||||
func receive(subscription: Subscription)
|
||||
|
||||
/// Tells the subscriber that the publisher has produced an element.
|
||||
///
|
||||
/// - Parameter input: The published element.
|
||||
/// - Returns: A `Subscribers.Demand` instance indicating how many more elements
|
||||
/// the subscriber expects to receive.
|
||||
func receive(_ input: Input) -> Subscribers.Demand
|
||||
|
||||
/// Tells the subscriber that the publisher has completed publishing, either normally
|
||||
/// or with an error.
|
||||
///
|
||||
/// - Parameter completion: A `Subscribers.Completion` case indicating whether
|
||||
/// publishing completed normally or with an error.
|
||||
func receive(completion: Subscribers.Completion<Failure>)
|
||||
}
|
||||
|
||||
/// A protocol that defines when and how to execute a closure.
|
||||
///
|
||||
/// You can use a scheduler to execute code as soon as possible, or after a future date.
|
||||
/// Individual scheduler implementations use whatever time-keeping system makes sense
|
||||
/// for them. Schedulers express this as their `SchedulerTimeType`. Since this type
|
||||
/// conforms to `SchedulerTimeIntervalConvertible`, you can always express these times
|
||||
/// with the convenience functions like `.milliseconds(500)`. Schedulers can accept
|
||||
/// options to control how they execute the actions passed to them. These options may
|
||||
/// control factors like which threads or dispatch queues execute the actions.
|
||||
public protocol Scheduler {
|
||||
|
||||
/// Describes an instant in time for this scheduler.
|
||||
associatedtype SchedulerTimeType: Strideable
|
||||
where SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible
|
||||
|
||||
/// A type that defines options accepted by the scheduler.
|
||||
///
|
||||
/// This type is freely definable by each `Scheduler`. Typically, operations that
|
||||
/// take a `Scheduler` parameter will also take `SchedulerOptions`.
|
||||
associatedtype SchedulerOptions
|
||||
|
||||
/// This scheduler’s definition of the current moment in time.
|
||||
var now: SchedulerTimeType { get }
|
||||
|
||||
/// The minimum tolerance allowed by the scheduler.
|
||||
var minimumTolerance: SchedulerTimeType.Stride { get }
|
||||
|
||||
/// Performs the action at the next possible opportunity.
|
||||
func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void)
|
||||
|
||||
/// Performs the action at some time after the specified date.
|
||||
func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void)
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
/// frequency, optionally taking into account tolerance if possible.
|
||||
func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// ConduitBase.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.06.2020.
|
||||
//
|
||||
|
||||
internal class ConduitBase<Output, Failure: Error>: Subscription {
|
||||
|
||||
internal init() {}
|
||||
|
||||
internal func offer(_ output: Output) {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal func finish(completion: Subscribers.Completion<Failure>) {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
abstractMethod()
|
||||
}
|
||||
}
|
||||
|
||||
extension ConduitBase: Equatable {
|
||||
internal static func == (lhs: ConduitBase<Output, Failure>,
|
||||
rhs: ConduitBase<Output, Failure>) -> Bool {
|
||||
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConduitBase: Hashable {
|
||||
internal func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// ConduitList.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.06.2020.
|
||||
//
|
||||
|
||||
internal enum ConduitList<Output, Failure: Error> {
|
||||
case empty
|
||||
case single(ConduitBase<Output, Failure>)
|
||||
case many(Set<ConduitBase<Output, Failure>>)
|
||||
}
|
||||
|
||||
extension ConduitList: HasDefaultValue {
|
||||
init() {
|
||||
self = .empty
|
||||
}
|
||||
}
|
||||
|
||||
extension ConduitList {
|
||||
internal mutating func insert(_ conduit: ConduitBase<Output, Failure>) {
|
||||
switch self {
|
||||
case .empty:
|
||||
self = .single(conduit)
|
||||
case .single(conduit):
|
||||
break // This element already exists.
|
||||
case .single(let existingConduit):
|
||||
self = .many([existingConduit, conduit])
|
||||
case .many(var set):
|
||||
set.insert(conduit)
|
||||
self = .many(set)
|
||||
}
|
||||
}
|
||||
|
||||
internal func forEach(
|
||||
_ body: (ConduitBase<Output, Failure>) throws -> Void
|
||||
) rethrows {
|
||||
switch self {
|
||||
case .empty:
|
||||
break
|
||||
case .single(let conduit):
|
||||
try body(conduit)
|
||||
case .many(let set):
|
||||
try set.forEach(body)
|
||||
}
|
||||
}
|
||||
|
||||
internal mutating func remove(_ conduit: ConduitBase<Output, Failure>) {
|
||||
switch self {
|
||||
case .single(conduit):
|
||||
self = .empty
|
||||
case .empty, .single:
|
||||
break
|
||||
case .many(var set):
|
||||
set.remove(conduit)
|
||||
self = .many(set)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// DebugHook.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
internal final class DebugHook {
|
||||
|
||||
private struct Handler: Hashable {
|
||||
let handler: _Introspection
|
||||
|
||||
static func == (lhs: Handler, rhs: Handler) -> Bool {
|
||||
return lhs.handler === rhs.handler
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(handler))
|
||||
}
|
||||
}
|
||||
|
||||
internal static func getGlobalHook() -> DebugHook? {
|
||||
globalLock.lock()
|
||||
defer { globalLock.unlock() }
|
||||
return globalHook
|
||||
}
|
||||
|
||||
internal static func enable(_ handler: _Introspection) {
|
||||
let hook: DebugHook
|
||||
DebugHook.globalLock.lock()
|
||||
defer { DebugHook.globalLock.unlock() }
|
||||
if let _hook = DebugHook.globalHook {
|
||||
hook = _hook
|
||||
} else {
|
||||
hook = DebugHook()
|
||||
DebugHook.globalHook = hook
|
||||
}
|
||||
hook.lock.lock()
|
||||
defer { hook.lock.unlock() }
|
||||
hook.handlers.insert(Handler(handler: handler))
|
||||
}
|
||||
|
||||
internal static func disable(_ handler: _Introspection) {
|
||||
DebugHook.globalLock.lock()
|
||||
defer { DebugHook.globalLock.unlock() }
|
||||
guard let hook = DebugHook.globalHook else { return }
|
||||
hook.lock.lock()
|
||||
hook.handlers.remove(Handler(handler: handler))
|
||||
let noMoreHandlers = hook.handlers.isEmpty
|
||||
hook.lock.unlock()
|
||||
if noMoreHandlers {
|
||||
DebugHook.globalHook = nil
|
||||
}
|
||||
}
|
||||
|
||||
internal static func handlerIsEnabled(_ handler: _Introspection) -> Bool {
|
||||
DebugHook.globalLock.lock()
|
||||
defer { DebugHook.globalLock.unlock() }
|
||||
guard let hook = DebugHook.globalHook else { return false }
|
||||
hook.lock.lock()
|
||||
defer { hook.lock.unlock() }
|
||||
return hook.handlers.contains(Handler(handler: handler))
|
||||
}
|
||||
|
||||
private static var globalHook: DebugHook?
|
||||
|
||||
private static let globalLock = UnfairLock.allocate()
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var handlers = Set<Handler>()
|
||||
|
||||
internal var debugHandlers: [_Introspection] {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return handlers.map { $0.handler }
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
internal func willReceive<Upstream: Publisher, Downstream: Subscriber>(
|
||||
publisher: Upstream,
|
||||
subscriber: Downstream
|
||||
) where Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(publisher: publisher, subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Upstream: Publisher, Downstream: Subscriber>(
|
||||
publisher: Upstream,
|
||||
subscriber: Downstream
|
||||
) where Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(publisher: publisher, subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willReceive<Downstream: Subscriber>(subscriber: Downstream,
|
||||
subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(subscriber: subscriber, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Downstream: Subscriber>(subscriber: Downstream,
|
||||
subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(subscriber: subscriber, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willReceive<Downstream: Subscriber>(subscriber: Downstream,
|
||||
input: Downstream.Input) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(subscriber: subscriber, input: input)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Downstream: Subscriber>(
|
||||
subscriber: Downstream,
|
||||
input: Downstream.Input,
|
||||
resultingDemand: Subscribers.Demand
|
||||
) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(subscriber: subscriber,
|
||||
input: input,
|
||||
resultingDemand: resultingDemand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willReceive<Downstream: Subscriber>(
|
||||
subscriber: Downstream,
|
||||
completion: Subscribers.Completion<Downstream.Failure>
|
||||
) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willReceive(subscriber: subscriber, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didReceive<Downstream: Subscriber>(
|
||||
subscriber: Downstream,
|
||||
completion: Subscribers.Completion<Downstream.Failure>
|
||||
) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didReceive(subscriber: subscriber, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willRequest(subscription: Subscription, demand: Subscribers.Demand) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willRequest(subscription: subscription, demand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didRequest(subscription: Subscription, demand: Subscribers.Demand) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didRequest(subscription: subscription, demand)
|
||||
}
|
||||
}
|
||||
|
||||
internal func willCancel(subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.willCancel(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func didCancel(subscription: Subscription) {
|
||||
for debugHandler in debugHandlers {
|
||||
debugHandler.didCancel(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// 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 subscriber (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
|
||||
{
|
||||
// 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,30 @@
|
||||
//
|
||||
// Locking.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
#if WASI
|
||||
internal struct __UnfairLock { // swiftlint:disable:this type_name
|
||||
internal static func allocate() -> UnfairLock { return .init() }
|
||||
internal func lock() {}
|
||||
internal func unlock() {}
|
||||
internal func assertOwner() {}
|
||||
internal func deallocate() {}
|
||||
}
|
||||
|
||||
internal struct __UnfairRecursiveLock { // swiftlint:disable:this type_name
|
||||
internal static func allocate() -> UnfairRecursiveLock { return .init() }
|
||||
internal func lock() {}
|
||||
internal func unlock() {}
|
||||
internal func deallocate() {}
|
||||
}
|
||||
#endif // WASI
|
||||
|
||||
internal typealias UnfairLock = __UnfairLock
|
||||
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
|
||||
@@ -0,0 +1,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,211 @@
|
||||
//
|
||||
// PublishedSubject.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 29.10.2020.
|
||||
//
|
||||
|
||||
internal final class PublishedSubject<Output>: Subject {
|
||||
|
||||
internal typealias Failure = Never
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
|
||||
private var currentValue: Output
|
||||
|
||||
private var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
private var hasAnyDownstreamDemand = false
|
||||
|
||||
private var changePublisher: ObservableObjectPublisher?
|
||||
|
||||
internal var value: Output {
|
||||
get {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return currentValue
|
||||
}
|
||||
set {
|
||||
send(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
internal var objectWillChange: ObservableObjectPublisher? {
|
||||
get {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return changePublisher
|
||||
}
|
||||
set {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
changePublisher = newValue
|
||||
}
|
||||
}
|
||||
|
||||
internal init(_ value: Output) {
|
||||
self.currentValue = value
|
||||
}
|
||||
|
||||
deinit {
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
internal func send(subscription: Subscription) {
|
||||
lock.lock()
|
||||
upstreamSubscriptions.append(subscription)
|
||||
lock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
lock.lock()
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
}
|
||||
|
||||
internal func send(_ input: Output) {
|
||||
lock.lock()
|
||||
let downstreams = self.downstreams
|
||||
let changePublisher = self.changePublisher
|
||||
lock.unlock()
|
||||
changePublisher?.send()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.offer(input)
|
||||
}
|
||||
lock.lock()
|
||||
currentValue = input
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
internal func send(completion: Subscribers.Completion<Never>) {
|
||||
fatalError("unreachable")
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension PublishedSubject {
|
||||
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
|
||||
fileprivate var parent: PublishedSubject?
|
||||
|
||||
fileprivate var downstream: Downstream?
|
||||
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var deliveredCurrentValue = false
|
||||
|
||||
fileprivate init(parent: PublishedSubject,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
lock.lock()
|
||||
guard demand > 0, let downstream = self.downstream else {
|
||||
deliveredCurrentValue = false
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
demand -= 1
|
||||
deliveredCurrentValue = true
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(output)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
if deliveredCurrentValue {
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Hasn't yet delivered the current value
|
||||
|
||||
self.demand += demand
|
||||
deliveredCurrentValue = true
|
||||
if let currentValue = self.parent?.value {
|
||||
self.demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(currentValue)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
self.demand += newDemand
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
var description: String { return "PublishedSubject" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("demand", demand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// PublishedSubscriber.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 29.10.2020.
|
||||
//
|
||||
|
||||
internal struct PublishedSubscriber<Value>: Subscriber {
|
||||
|
||||
internal typealias Input = Value
|
||||
|
||||
internal typealias Failure = Never
|
||||
|
||||
internal let combineIdentifier = CombineIdentifier()
|
||||
|
||||
private weak var subject: PublishedSubject<Value>?
|
||||
|
||||
internal init(_ subject: PublishedSubject<Value>) {
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
subject?.send(subscription: subscription)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Value) -> Subscribers.Demand {
|
||||
subject?.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Never>) {}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
//
|
||||
// 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 subscriber (as subscription).
|
||||
///
|
||||
/// 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
|
||||
|
||||
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
|
||||
if downstreamRequested {
|
||||
self.completed = true
|
||||
}
|
||||
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
|
||||
}
|
||||
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,96 @@
|
||||
//
|
||||
// SubjectSubscriber.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16/09/2019.
|
||||
//
|
||||
|
||||
internal final class SubjectSubscriber<Downstream: Subject>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible,
|
||||
Subscription
|
||||
{
|
||||
private let lock = UnfairLock.allocate()
|
||||
private weak 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 subject = downstreamSubject, upstreamSubscription != nil else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
subject.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
lock.lock()
|
||||
guard let subject = downstreamSubject, upstreamSubscription != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
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()
|
||||
guard !isCancelled, let subscription = upstreamSubscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = nil
|
||||
downstreamSubject = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// SubscriberTap.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
internal protocol SubscriberTapMarker {
|
||||
var inner: Any { mutating get }
|
||||
}
|
||||
|
||||
internal struct SubscriberTap<Subscriber: OpenCombine.Subscriber>
|
||||
: OpenCombine.Subscriber,
|
||||
CustomStringConvertible,
|
||||
SubscriberTapMarker
|
||||
{
|
||||
internal typealias Input = Subscriber.Input
|
||||
|
||||
internal typealias Failure = Subscriber.Failure
|
||||
|
||||
private let subscriber: Subscriber
|
||||
|
||||
internal lazy var inner: Any = AnySubscriber(self.subscriber)
|
||||
|
||||
internal init(subscriber: Subscriber) {
|
||||
self.subscriber = subscriber
|
||||
}
|
||||
|
||||
internal var combineIdentifier: CombineIdentifier {
|
||||
return subscriber.combineIdentifier
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
if let subscriptionTap = subscription as? SubscriptionTap {
|
||||
hook?.willReceive(subscriber: subscriber,
|
||||
subscription: subscriptionTap.subscription)
|
||||
subscriber.receive(subscription: subscriptionTap)
|
||||
hook?.didReceive(subscriber: subscriber,
|
||||
subscription: subscriptionTap.subscription)
|
||||
} else {
|
||||
hook?.willReceive(subscriber: subscriber, subscription: subscription)
|
||||
subscriber
|
||||
.receive(subscription: SubscriptionTap(subscription: subscription))
|
||||
hook?.didReceive(subscriber: subscriber, subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willReceive(subscriber: subscriber, input: input)
|
||||
let newDemand = subscriber.receive(input)
|
||||
hook?.didReceive(subscriber: subscriber,
|
||||
input: input,
|
||||
resultingDemand: newDemand)
|
||||
return newDemand
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Subscriber.Failure>) {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willReceive(subscriber: subscriber, completion: completion)
|
||||
subscriber.receive(completion: completion)
|
||||
hook?.didReceive(subscriber: subscriber, completion: completion)
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
return String(describing: subscriber)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// SubscriptionStatus.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 21.09.2019.
|
||||
//
|
||||
|
||||
internal enum SubscriptionStatus {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case pendingTerminal(Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
extension SubscriptionStatus {
|
||||
internal var isAwaitingSubscription: Bool {
|
||||
switch self {
|
||||
case .awaitingSubscription:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal var subscription: Subscription? {
|
||||
switch self {
|
||||
case .awaitingSubscription, .terminal:
|
||||
return nil
|
||||
case let .subscribed(subscription), let .pendingTerminal(subscription):
|
||||
return subscription
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// SubscriptionTap.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
internal struct SubscriptionTap: Subscription, CustomStringConvertible {
|
||||
|
||||
internal let subscription: Subscription
|
||||
|
||||
internal var combineIdentifier: CombineIdentifier {
|
||||
return subscription.combineIdentifier
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willRequest(subscription: subscription, demand: demand)
|
||||
subscription.request(demand)
|
||||
hook?.didRequest(subscription: subscription, demand: demand)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
let hook = DebugHook.getGlobalHook()
|
||||
hook?.willCancel(subscription: subscription)
|
||||
subscription.cancel()
|
||||
hook?.didCancel(subscription: subscription)
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
return String(describing: subscription)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Utils.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.08.2021.
|
||||
//
|
||||
|
||||
internal protocol HasDefaultValue {
|
||||
init()
|
||||
}
|
||||
|
||||
extension HasDefaultValue {
|
||||
|
||||
@inline(__always)
|
||||
internal mutating func take() -> Self {
|
||||
let taken = self
|
||||
self = .init()
|
||||
return taken
|
||||
}
|
||||
}
|
||||
|
||||
extension Array: HasDefaultValue {}
|
||||
|
||||
extension Dictionary: HasDefaultValue {}
|
||||
|
||||
extension Optional: HasDefaultValue {
|
||||
init() {
|
||||
self = nil
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -40,29 +41,42 @@ public struct ImmediateScheduler: Scheduler {
|
||||
Codable,
|
||||
SchedulerTimeIntervalConvertible {
|
||||
|
||||
/// The type used when evaluating floating-point literals.
|
||||
public typealias FloatLiteralType = Double
|
||||
|
||||
/// The type used when evaluating integer literals.
|
||||
public typealias IntegerLiteralType = Int
|
||||
|
||||
/// The type used for expressing the stride’s magnitude.
|
||||
public typealias Magnitude = Int
|
||||
|
||||
/// The value of this time interval in seconds.
|
||||
public var magnitude: Int
|
||||
|
||||
/// Creates an immediate scheduler time interval from the given time interval.
|
||||
@inlinable
|
||||
public init(_ value: Int) {
|
||||
magnitude = value
|
||||
}
|
||||
|
||||
/// Creates an immediate scheduler time interval from an integer seconds
|
||||
/// value.
|
||||
@inlinable
|
||||
public init(integerLiteral value: Int) {
|
||||
self.init(value)
|
||||
}
|
||||
|
||||
/// Creates an immediate scheduler time interval from a floating-point seconds
|
||||
/// value.
|
||||
@inlinable
|
||||
public init(floatLiteral value: Double) {
|
||||
self.init(Int(value))
|
||||
}
|
||||
|
||||
/// Creates an immediate scheduler time interval from a binary integer type.
|
||||
///
|
||||
/// If `exactly` can’t convert to an `Int`, the resulting time interval is
|
||||
/// `nil`.
|
||||
@inlinable
|
||||
public init?<BinaryIntegerType: BinaryInteger>(
|
||||
exactly source: BinaryIntegerType
|
||||
@@ -118,6 +132,7 @@ public struct ImmediateScheduler: Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that defines options accepted by the immediate scheduler.
|
||||
public typealias SchedulerOptions = Never
|
||||
|
||||
/// The shared instance of the immediate scheduler.
|
||||
@@ -126,15 +141,21 @@ public struct ImmediateScheduler: Scheduler {
|
||||
/// the shared instance.
|
||||
public static let shared = ImmediateScheduler()
|
||||
|
||||
/// Performs the action at the next possible opportunity.
|
||||
@inlinable
|
||||
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
|
||||
action()
|
||||
}
|
||||
|
||||
/// The immediate scheduler’s definition of the current moment in time.
|
||||
public var now: SchedulerTimeType { return SchedulerTimeType() }
|
||||
|
||||
/// The minimum tolerance allowed by the immediate scheduler.
|
||||
public var minimumTolerance: SchedulerTimeType.Stride { return 0 }
|
||||
|
||||
/// Performs the action at some time after the specified date.
|
||||
///
|
||||
/// The immediate scheduler ignores `date` and performs the action immediately.
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
@@ -144,6 +165,8 @@ public struct ImmediateScheduler: Scheduler {
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
/// frequency, optionally taking into account tolerance if possible.
|
||||
///
|
||||
/// The immediate scheduler ignores `date` and performs the action immediately.
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
|
||||
@@ -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,227 @@
|
||||
//
|
||||
// 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` synthesizes an `objectWillChange` publisher that
|
||||
/// emits the changed value 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)
|
||||
/// cancellable = john.objectWillChange
|
||||
/// .sink { _ in
|
||||
/// print("\(john.age) will change")
|
||||
/// }
|
||||
/// print(john.haveBirthday())
|
||||
/// // Prints "24 will change"
|
||||
/// // Prints "25"
|
||||
public protocol ObservableObject: AnyObject {
|
||||
|
||||
/// The type of publisher that emits before the object has changed.
|
||||
associatedtype ObjectWillChangePublisher: Publisher = ObservableObjectPublisher
|
||||
where ObjectWillChangePublisher.Failure == Never
|
||||
|
||||
/// A publisher that emits before the object has changed.
|
||||
var objectWillChange: ObjectWillChangePublisher { get }
|
||||
}
|
||||
|
||||
private protocol _ObservableObjectProperty {
|
||||
var objectWillChange: ObservableObjectPublisher? { get nonmutating set }
|
||||
}
|
||||
|
||||
#if swift(>=5.1)
|
||||
extension Published: _ObservableObjectProperty {}
|
||||
|
||||
extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
|
||||
/// A publisher that emits before the object has changed.
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
var installedPublisher: ObservableObjectPublisher?
|
||||
var reflection: Mirror? = Mirror(reflecting: self)
|
||||
while let aClass = reflection {
|
||||
for (_, property) in aClass.children {
|
||||
guard let property = property as? _ObservableObjectProperty else {
|
||||
// Visit other fields until we meet a @Published field
|
||||
continue
|
||||
}
|
||||
|
||||
// Now we know that the field is @Published.
|
||||
if let alreadyInstalledPublisher = property.objectWillChange {
|
||||
installedPublisher = alreadyInstalledPublisher
|
||||
// Don't visit other fields, as all @Published fields
|
||||
// already have a publisher installed.
|
||||
break
|
||||
}
|
||||
|
||||
// Okay, this field doesn't have a publisher installed.
|
||||
// This means that other fields don't have it either
|
||||
// (because we install it only once and fields can't be added at runtime).
|
||||
var lazilyCreatedPublisher: ObjectWillChangePublisher {
|
||||
if let publisher = installedPublisher {
|
||||
return publisher
|
||||
}
|
||||
let publisher = ObservableObjectPublisher()
|
||||
installedPublisher = publisher
|
||||
return publisher
|
||||
}
|
||||
|
||||
property.objectWillChange = lazilyCreatedPublisher
|
||||
|
||||
// Continue visiting other fields.
|
||||
}
|
||||
reflection = aClass.superclassMirror
|
||||
}
|
||||
return installedPublisher ?? ObservableObjectPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// A publisher that publishes changes from observable objects.
|
||||
public final class ObservableObjectPublisher: Publisher {
|
||||
|
||||
public typealias Output = Void
|
||||
|
||||
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?
|
||||
|
||||
/// Creates an observable object publisher instance.
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,24 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
/// A subject that passes along values and completion.
|
||||
/// A subject that broadcasts elements to downstream subscribers.
|
||||
///
|
||||
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
|
||||
/// specific values on-demand during tests.
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
/// As a concrete implementation of `Subject`, the `PassthroughSubject` provides
|
||||
/// a convenient way to adapt existing imperative code to the Combine model.
|
||||
///
|
||||
/// Unlike `CurrentValueSubject`, a `PassthroughSubject` doesn’t have an initial value or
|
||||
/// a buffer of the most recently-published element.
|
||||
/// A `PassthroughSubject` drops values if there are no subscribers, or its current demand
|
||||
/// is zero.
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _lock = Lock(recursive: true)
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
private var active = true
|
||||
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
private var completion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var downstreams = ConduitList<Output, Failure>.empty
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
@@ -25,111 +31,192 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
public init() {}
|
||||
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
if hasAnyDownstreamDemand {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
lock.lock()
|
||||
upstreamSubscriptions.append(subscription)
|
||||
let hasAnyDownstreamDemand = self.hasAnyDownstreamDemand
|
||||
lock.unlock()
|
||||
if hasAnyDownstreamDemand {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
lock.lock()
|
||||
if active {
|
||||
let conduit = Conduit(parent: self, downstream: subscriber)
|
||||
downstreams.insert(conduit)
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: conduit)
|
||||
} else {
|
||||
let completion = self.completion!
|
||||
lock.unlock()
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
_lock.do {
|
||||
for subscription in _subscriptions
|
||||
where !subscription._isCompleted && subscription._demand > 0
|
||||
{
|
||||
let newDemand = subscription._downstream?.receive(input) ?? .none
|
||||
subscription._demand += newDemand
|
||||
subscription._demand -= 1
|
||||
}
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let downstreams = self.downstreams
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.offer(input)
|
||||
}
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
_lock.do {
|
||||
_completion = completion
|
||||
for subscriber in _subscriptions {
|
||||
subscriber._receive(completion: completion)
|
||||
}
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
active = false
|
||||
self.completion = completion
|
||||
let downstreams = self.downstreams.take()
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.finish(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func _acknowledgeDownstreamDemand() {
|
||||
_lock.do {
|
||||
guard !hasAnyDownstreamDemand else { return }
|
||||
hasAnyDownstreamDemand = true
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
private func acknowledgeDownstreamDemand() {
|
||||
lock.lock()
|
||||
if hasAnyDownstreamDemand {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
hasAnyDownstreamDemand = true
|
||||
let upstreamSubscriptions = self.upstreamSubscriptions
|
||||
lock.unlock()
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard active else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreams.remove(conduit)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject {
|
||||
|
||||
fileprivate final class Conduit: Subscription {
|
||||
private final class Conduit<Downstream: Subscriber>
|
||||
: ConduitBase<Output, Failure>,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
|
||||
fileprivate var _parent: PassthroughSubject?
|
||||
fileprivate var parent: PassthroughSubject?
|
||||
|
||||
fileprivate var _downstream: AnySubscriber<Output, Failure>?
|
||||
fileprivate var downstream: Downstream?
|
||||
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
fileprivate var demand = Subscribers.Demand.none
|
||||
|
||||
fileprivate var _isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(parent: PassthroughSubject,
|
||||
downstream: AnySubscriber<Output, Failure>) {
|
||||
_parent = parent
|
||||
_downstream = downstream
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !_isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
override func offer(_ output: Output) {
|
||||
lock.lock()
|
||||
guard demand > 0, let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(output)
|
||||
downstreamLock.unlock()
|
||||
guard newDemand > 0 else { return }
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
precondition(demand > 0, "demand must not be zero")
|
||||
_parent?._lock.do {
|
||||
_demand = demand
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
downstreamLock.lock()
|
||||
downstream.receive(completion: completion)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
fileprivate func cancel() {
|
||||
_parent = nil
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.demand += demand
|
||||
let parent = self.parent
|
||||
lock.unlock()
|
||||
parent?.acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if downstream.take() == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
var description: String { return "PassthroughSubject" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("demand", demand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "PassthroughSubject" }
|
||||
}
|
||||
|
||||
@@ -6,92 +6,221 @@
|
||||
//
|
||||
|
||||
#if swift(>=5.1)
|
||||
/// Adds a `Publisher` to a property.
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Republishes elements received from a publisher, by assigning them to a property
|
||||
/// marked as a publisher.
|
||||
///
|
||||
/// Use this operator when you want to receive elements from a publisher and republish
|
||||
/// them through a property marked with the `@Published` attribute. The `assign(to:)`
|
||||
/// operator manages the life cycle of the subscription, canceling the subscription
|
||||
/// automatically when the `Published` instance deinitializes. Because of this,
|
||||
/// the `assign(to:)` operator doesn't return an `AnyCancellable` that you're
|
||||
/// responsible for like `assign(to:on:)` does.
|
||||
///
|
||||
/// The example below shows a model class that receives elements from an internal
|
||||
/// `Timer.TimerPublisher`, and assigns them to a `@Published` property called
|
||||
/// `lastUpdated`:
|
||||
///
|
||||
/// class MyModel: ObservableObject {
|
||||
/// @Published var lastUpdated: Date = Date()
|
||||
/// init() {
|
||||
/// Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
/// .autoconnect()
|
||||
/// .assign(to: $lastUpdated)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// If you instead implemented `MyModel` with `assign(to: lastUpdated, on: self)`,
|
||||
/// storing the returned `AnyCancellable` instance could cause a reference cycle,
|
||||
/// because the `Subscribers.Assign` subscriber would hold a strong reference
|
||||
/// to `self`. Using `assign(to:)` solves this problem.
|
||||
///
|
||||
/// - Parameter published: A property marked with the `@Published` attribute, which
|
||||
/// receives and republishes all elements received from the upstream publisher.
|
||||
public func assign(to published: inout Published<Output>.Publisher) {
|
||||
subscribe(PublishedSubscriber(published.subject))
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that publishes a property marked with an attribute.
|
||||
///
|
||||
/// Properties annotated with `@Published` contain both the stored value
|
||||
/// and a publisher which sends any new values after the property value
|
||||
/// has been sent. New subscribers will receive the current value
|
||||
/// of the property first.
|
||||
@propertyWrapper public struct Published<Value> {
|
||||
|
||||
/// Initialize the storage of the Published
|
||||
/// property as well as the corresponding `Publisher`.
|
||||
public init(initialValue: Value) {
|
||||
value = initialValue
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
public init(wrappedValue: Value) {
|
||||
value = wrappedValue
|
||||
}
|
||||
/// Publishing a property with the `@Published` attribute creates a publisher of this
|
||||
/// type. You access the publisher with the `$` operator, as shown here:
|
||||
///
|
||||
/// class Weather {
|
||||
/// @Published var temperature: Double
|
||||
/// init(temperature: Double) {
|
||||
/// self.temperature = temperature
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let weather = Weather(temperature: 20)
|
||||
/// cancellable = weather.$temperature
|
||||
/// .sink() {
|
||||
/// print ("Temperature now: \($0)")
|
||||
/// }
|
||||
/// weather.temperature = 25
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Temperature now: 20.0
|
||||
/// // Temperature now: 25.0
|
||||
///
|
||||
/// When the property changes, publishing occurs in the property's `willSet` block,
|
||||
/// meaning subscribers receive the new value before it's actually set on the property.
|
||||
/// In the above example, the second time the sink executes its closure, it receives
|
||||
/// the parameter value `25`. However, if the closure evaluated `weather.temperature`,
|
||||
/// the value returned would be `20`.
|
||||
///
|
||||
/// > Important: The `@Published` attribute is class constrained. Use it with properties
|
||||
/// of classes, not with non-class types like structures.
|
||||
///
|
||||
/// ### See Also
|
||||
///
|
||||
/// - `Publisher.assign(to:)`
|
||||
@available(swift, introduced: 5.1)
|
||||
@propertyWrapper
|
||||
public struct Published<Value> {
|
||||
|
||||
/// A publisher for properties marked with the `@Published` attribute.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Value
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// 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.
|
||||
fileprivate let subject: PublishedSubject<Value>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Value, Downstream.Failure == Never
|
||||
{
|
||||
subject.subscribe(subscriber)
|
||||
}
|
||||
|
||||
fileprivate let subject: OpenCombine.CurrentValueSubject<Value, Never>
|
||||
|
||||
fileprivate init(_ output: Output) {
|
||||
subject = .init(output)
|
||||
}
|
||||
}
|
||||
|
||||
private var value: Value
|
||||
private enum Storage {
|
||||
case value(Value)
|
||||
case publisher(Publisher)
|
||||
}
|
||||
@propertyWrapper
|
||||
private final class Box {
|
||||
var wrappedValue: Storage
|
||||
|
||||
/// The property that can be accessed with the
|
||||
/// `$` syntax and allows access to the `Publisher`
|
||||
init(wrappedValue: Storage) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
@Box private var storage: Storage
|
||||
|
||||
internal var objectWillChange: ObservableObjectPublisher? {
|
||||
get {
|
||||
switch storage {
|
||||
case .value:
|
||||
return nil
|
||||
case .publisher(let publisher):
|
||||
return publisher.subject.objectWillChange
|
||||
}
|
||||
}
|
||||
nonmutating set {
|
||||
getPublisher().subject.objectWillChange = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the published instance with an initial wrapped value.
|
||||
///
|
||||
/// Don't use this initializer directly. Instead, create a property with
|
||||
/// the `@Published` attribute, as shown here:
|
||||
///
|
||||
/// @Published var lastUpdated: Date = Date()
|
||||
///
|
||||
/// - Parameter wrappedValue: The publisher's initial value.
|
||||
public init(initialValue: Value) {
|
||||
self.init(wrappedValue: initialValue)
|
||||
}
|
||||
|
||||
/// Creates the published instance with an initial value.
|
||||
///
|
||||
/// Don't use this initializer directly. Instead, create a property with
|
||||
/// the `@Published` attribute, as shown here:
|
||||
///
|
||||
/// @Published var lastUpdated: Date = Date()
|
||||
///
|
||||
/// - Parameter initialValue: The publisher's initial value.
|
||||
public init(wrappedValue: Value) {
|
||||
_storage = Box(wrappedValue: .value(wrappedValue))
|
||||
}
|
||||
|
||||
/// The property for which this instance exposes a publisher.
|
||||
///
|
||||
/// The `projectedValue` is the property accessed with the `$` operator.
|
||||
public var projectedValue: Publisher {
|
||||
mutating get {
|
||||
if let publisher = publisher {
|
||||
return publisher
|
||||
return getPublisher()
|
||||
}
|
||||
set { // swiftlint:disable:this unused_setter_value
|
||||
switch storage {
|
||||
case .value(let value):
|
||||
storage = .publisher(Publisher(value))
|
||||
case .publisher:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This method can mutate `storage`
|
||||
internal func getPublisher() -> Publisher {
|
||||
switch storage {
|
||||
case .value(let value):
|
||||
let publisher = Publisher(value)
|
||||
self.publisher = publisher
|
||||
storage = .publisher(publisher)
|
||||
return publisher
|
||||
case .publisher(let publisher):
|
||||
return publisher
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable, message:
|
||||
"@Published is only available on properties of classes")
|
||||
|
||||
// swiftlint:disable let_var_whitespace
|
||||
@available(*, unavailable, message: """
|
||||
@Published is only available on properties of classes
|
||||
""")
|
||||
public var wrappedValue: Value {
|
||||
get { value }
|
||||
set {
|
||||
value = newValue
|
||||
publisher?.subject.value = newValue
|
||||
}
|
||||
get { fatalError() }
|
||||
set { fatalError() } // swiftlint:disable:this unused_setter_value
|
||||
}
|
||||
// swiftlint:enable let_var_whitespace
|
||||
|
||||
private var publisher: Publisher?
|
||||
|
||||
@available(*, unavailable, message:
|
||||
"This subscript is unavailable in OpenCombine yet")
|
||||
public static subscript<EnclosingSelf: AnyObject>(
|
||||
_enclosingInstance object: EnclosingSelf,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
|
||||
) -> Value {
|
||||
get { fatalError() }
|
||||
set { fatalError() }
|
||||
get {
|
||||
switch object[keyPath: storageKeyPath].storage {
|
||||
case .value(let value):
|
||||
return value
|
||||
case .publisher(let publisher):
|
||||
return publisher.subject.value
|
||||
}
|
||||
}
|
||||
set {
|
||||
switch object[keyPath: storageKeyPath].storage {
|
||||
case .value:
|
||||
object[keyPath: storageKeyPath].storage = .publisher(Publisher(newValue))
|
||||
case .publisher(let publisher):
|
||||
publisher.subject.value = newValue
|
||||
}
|
||||
}
|
||||
// TODO: Benchmark and explore a possibility to use _modify
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
|
||||
@available(swift, introduced: 5.1)
|
||||
public typealias Published = Never
|
||||
|
||||
#endif // swift(>=5.1)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Publisher+Subscribe.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23.04.2023.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// Always call this function instead of `receive(subscriber:)`.
|
||||
/// Adopters of `Publisher` must implement `receive(subscriber:)`.
|
||||
/// The implementation of `subscribe(_:)` in this extension calls through to
|
||||
/// `receive(subscriber:)`.
|
||||
/// - SeeAlso: `receive(subscriber:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`. After attaching,
|
||||
/// the subscriber can start to receive values.
|
||||
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
{
|
||||
if let hook = DebugHook.getGlobalHook() {
|
||||
if var marker = subscriber as? SubscriberTapMarker {
|
||||
let anySubscriber = marker.inner
|
||||
as! AnySubscriber<Subscriber.Input, Subscriber.Failure>
|
||||
hook.willReceive(publisher: self, subscriber: anySubscriber)
|
||||
receive(subscriber: subscriber)
|
||||
hook.didReceive(publisher: self, subscriber: anySubscriber)
|
||||
} else {
|
||||
let tap = SubscriberTap(subscriber: subscriber)
|
||||
hook.willReceive(publisher: self, subscriber: subscriber)
|
||||
receive(subscriber: tap)
|
||||
hook.didReceive(publisher: self, subscriber: subscriber)
|
||||
}
|
||||
} else {
|
||||
receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches the specified subject to this publisher.
|
||||
///
|
||||
/// - Parameter subject: The subject to attach to this publisher.
|
||||
public func subscribe<Subject: OpenCombine.Subject>(
|
||||
_ subject: Subject
|
||||
) -> AnyCancellable
|
||||
where Failure == Subject.Failure, Output == Subject.Output
|
||||
{
|
||||
let subscriber = SubjectSubscriber(subject)
|
||||
self.subscribe(subscriber)
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
//
|
||||
// Publisher.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// Declares that a type can transmit a sequence of values over time.
|
||||
///
|
||||
/// There are four kinds of messages:
|
||||
/// subscription - A connection between `Publisher` and `Subscriber`.
|
||||
/// value - An element in the sequence.
|
||||
/// error - The sequence ended with an error (`.failure(e)`).
|
||||
/// complete - The sequence ended successfully (`.finished`).
|
||||
///
|
||||
/// Both `.failure` and `.finished` are terminal messages.
|
||||
///
|
||||
/// You can summarize these possibilities with a regular expression:
|
||||
/// value*(error|finished)?
|
||||
///
|
||||
/// Every `Publisher` must adhere to this contract.
|
||||
public protocol Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
associatedtype Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
associatedtype Failure: Error
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher`
|
||||
/// by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// Always call this function instead of `receive(subscriber:)`.
|
||||
/// Adopters of `Publisher` must implement `receive(subscriber:)`.
|
||||
/// The implementation of `subscribe(_:)` in this extension calls through to
|
||||
/// `receive(subscriber:)`.
|
||||
/// - SeeAlso: `receive(subscriber:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`. After attaching,
|
||||
/// the subscriber can start to receive values.
|
||||
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
{
|
||||
receive(subscriber: subscriber)
|
||||
}
|
||||
|
||||
public func subscribe<Subject: OpenCombine.Subject>(
|
||||
_ subject: Subject
|
||||
) -> AnyCancellable
|
||||
where Failure == Subject.Failure, Output == Subject.Output
|
||||
{
|
||||
let subscriber = SubjectSubscriber(subject)
|
||||
self.subscribe(subscriber)
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public struct Deferred<DeferredPublisher: Publisher>: Publisher {
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = DeferredPublisher.Failure
|
||||
|
||||
/// The closure to execute when it receives a subscription.
|
||||
/// The closure to execute when this deferred publisher receives a subscription.
|
||||
///
|
||||
/// The publisher returned by this closure immediately
|
||||
/// receives the incoming subscription.
|
||||
@@ -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)
|
||||
|
||||
@@ -24,6 +24,7 @@ public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
///
|
||||
/// Use this initializer to connect the empty publisher to subscribers or other
|
||||
/// publishers that have specific output and failure types.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - completeImmediately: A Boolean value that indicates whether the publisher
|
||||
/// should immediately finish.
|
||||
@@ -42,8 +43,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 {
|
||||
|
||||
@@ -20,6 +20,7 @@ public struct Fail<Output, Failure: Error>: Publisher {
|
||||
///
|
||||
/// Use this initializer to create a `Fail` publisher that can work with
|
||||
/// subscribers or publishers that expect a given output type.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failure: The failure to send when terminating the publisher.
|
||||
@@ -30,8 +31,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,673 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.Catch.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Handles errors from an upstream publisher by replacing it with another publisher.
|
||||
///
|
||||
/// Use `catch()` to replace an error from an upstream publisher with a new publisher.
|
||||
///
|
||||
/// In the example below, the `catch()` operator handles the `SimpleError` thrown by
|
||||
/// the upstream publisher by replacing the error with a `Just` publisher. This
|
||||
/// continues the stream by publishing a single value and completing normally.
|
||||
///
|
||||
/// struct SimpleError: Error {}
|
||||
/// let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryLast(where: {
|
||||
/// guard $0 != 0 else { throw SimpleError() }
|
||||
/// return true
|
||||
/// })
|
||||
/// .catch { error in
|
||||
/// Just(-1)
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
/// // Prints: -1
|
||||
///
|
||||
/// Backpressure note: This publisher passes through `request` and `cancel` to
|
||||
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
|
||||
/// demand to the new `Publisher`.
|
||||
///
|
||||
/// - SeeAlso: `replaceError`
|
||||
/// - Parameter handler: A closure that accepts the upstream failure as input and
|
||||
/// returns a publisher to replace the upstream publisher.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
public func `catch`<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) -> NewPublisher
|
||||
) -> Publishers.Catch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another
|
||||
/// publisher or throwing a new error.
|
||||
///
|
||||
/// Use `tryCatch(_:)` to decide how to handle from an upstream publisher by either
|
||||
/// replacing the publisher with a new publisher, or throwing a new error.
|
||||
///
|
||||
/// In the example below, an array publisher emits values that a `tryMap(_:)` operator
|
||||
/// evaluates to ensure the values are greater than zero. If the values aren’t greater
|
||||
/// than zero, the operator throws an error to the downstream subscriber to let it
|
||||
/// know there was a problem. The subscriber, `tryCatch(_:)`, replaces the error with
|
||||
/// a new publisher using ``Just`` to publish a final value before the stream ends
|
||||
/// normally.
|
||||
///
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// var numbers = [5, 4, 3, 2, 1, -1, 7, 8, 9, 10]
|
||||
///
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMap { v in
|
||||
/// if v > 0 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
/// .tryCatch { error in
|
||||
/// Just(0) // Send a final value before completing normally.
|
||||
/// // Alternatively, throw a new error to terminate the stream.
|
||||
/// }
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0).") },
|
||||
/// receiveValue: { print ("Received \($0).") })
|
||||
/// // Received 5.
|
||||
/// // Received 4.
|
||||
/// // Received 3.
|
||||
/// // Received 2.
|
||||
/// // Received 1.
|
||||
/// // Received 0.
|
||||
/// // Completion: finished.
|
||||
///
|
||||
/// - Parameter handler: A throwing closure that accepts the upstream failure as
|
||||
/// input. This closure can either replace the upstream publisher with a new one,
|
||||
/// or throw a new error to the downstream subscriber.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher, or an error.
|
||||
public func tryCatch<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) throws -> NewPublisher
|
||||
) -> Publishers.TryCatch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher.
|
||||
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = NewPublisher.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and returns a publisher
|
||||
/// to replace the upstream publisher.
|
||||
public let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and returns
|
||||
/// a publisher to replace the upstream publisher.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher or producing a new error.
|
||||
///
|
||||
/// Because this publisher’s handler can throw an error, `Publishers.TryCatch` defines
|
||||
/// its `Failure` type as `Error`. This is different from `Publishers.Catch`, which
|
||||
/// gets its failure type from the replacement publisher.
|
||||
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and either returns
|
||||
/// a publisher to replace the upstream publisher or throws an error.
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher or by throwing an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and either
|
||||
/// returns a publisher to replace the upstream publisher. If this closure
|
||||
/// throws an error, the publisher terminates with the thrown error.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Catch {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output,
|
||||
Downstream.Failure == NewPublisher.Failure
|
||||
{
|
||||
struct UncaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePre(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePre(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
return inner.receivePre(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
struct CaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NewPublisher.Output
|
||||
|
||||
typealias Failure = NewPublisher.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePost(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePost(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.receivePost(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case pendingPre
|
||||
case pre(Subscription)
|
||||
case pendingPost
|
||||
case post(Subscription)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var state = State.pendingPre
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
init(downstream: Downstream,
|
||||
handler: @escaping (Upstream.Failure) -> NewPublisher) {
|
||||
self.downstream = downstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receivePre(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPre = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .pre(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .pendingPre, .pendingPost, .post, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure(let error):
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .pendingPost
|
||||
lock.unlock()
|
||||
handler(error).subscribe(CaughtS(inner: self))
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
case .pendingPre, .post, .pendingPost:
|
||||
completionBeforeSubscription()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPost = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .post(subscription)
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
|
||||
lock.lock()
|
||||
guard case .post = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pendingPre:
|
||||
// The client is only able to call the `request` method after we've sent
|
||||
// `self` downstream. We only do it in the `receivePre(subscription:)`
|
||||
// method, after setting `state` to `pre`.
|
||||
// After that `state` never becomes `pendingPre`.
|
||||
requestBeforeSubscription()
|
||||
case let .pre(subscription):
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .pendingPost:
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
case let .post(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .pre(subscription), let .post(subscription):
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
case .pendingPre, .pendingPost, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "Catch" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCatch {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output,
|
||||
Downstream.Failure == Error
|
||||
{
|
||||
struct UncaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePre(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePre(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
return inner.receivePre(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
struct CaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NewPublisher.Output
|
||||
|
||||
typealias Failure = NewPublisher.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePost(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePost(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.receivePost(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case pendingPre
|
||||
case pre(Subscription)
|
||||
case pendingPost
|
||||
case post(Subscription)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var state = State.pendingPre
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
init(downstream: Downstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.downstream = downstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receivePre(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPre = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .pre(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .pendingPre, .pendingPost, .post, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure(let error):
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .pendingPost
|
||||
lock.unlock()
|
||||
do {
|
||||
try handler(error).subscribe(CaughtS(inner: self))
|
||||
} catch let anotherError {
|
||||
lock.lock()
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .failure(anotherError))
|
||||
}
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
case .pendingPre, .post, .pendingPost:
|
||||
completionBeforeSubscription()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPost = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .post(subscription)
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
|
||||
lock.lock()
|
||||
guard case .post = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pendingPre:
|
||||
// The client is only able to call the `request` method after we've sent
|
||||
// `self` downstream. We only do it in the `receivePre(subscription:)`
|
||||
// method, after setting `state` to `pre`.
|
||||
// After that `state` never becomes `pendingPre`.
|
||||
requestBeforeSubscription()
|
||||
case let .pre(subscription):
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .pendingPost:
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
case let .post(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .pre(subscription), let .post(subscription):
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
case .pendingPre, .pendingPost, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "TryCatch" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
private func completionBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: received completion but do not have subscription",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
private func requestBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: request before subscription sent",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ 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 encoder.
|
||||
///
|
||||
/// Use `encode(encoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to encode an `Encodable` struct into `Data` that could be used to
|
||||
/// make a JSON string (or written to disk as a binary plist in the case of property
|
||||
/// lists).
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes an `Article`.
|
||||
/// The `encode(encoder:)` operator encodes the properties of the `Article` struct
|
||||
/// into a new JSON string according to the `Codable` protocol adopted by `Article`.
|
||||
/// The operator publishes the resulting JSON string to the downstream subscriber.
|
||||
/// If the encoding operation fails, which can happen in the case of complex
|
||||
/// properties that can’t be directly transformed into JSON, the stream terminates
|
||||
/// and the error is passed to the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Article, Never>()
|
||||
/// let cancellable = dataProvider
|
||||
/// .encode(encoder: JSONEncoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)") },
|
||||
/// receiveValue: { data in
|
||||
/// guard let stringRepresentation =
|
||||
/// String(data: data, encoding: .utf8) else { return }
|
||||
/// print("""
|
||||
/// Data received \(data) string representation: \
|
||||
/// \(stringRepresentation)
|
||||
/// """)
|
||||
/// })
|
||||
///
|
||||
/// dataProvider.send(Article(title: "My First Article",
|
||||
/// author: "Gita Kumar",
|
||||
/// pubDate: Date()))
|
||||
///
|
||||
/// // Prints: "Data received 86 bytes string representation:
|
||||
/// // {"title":"My First Article","author":"Gita Kumar"
|
||||
/// // "pubDate":606211803.279603}"
|
||||
///
|
||||
/// - Parameter encoder: An encoder that implements the `TopLevelEncoder` protocol.
|
||||
/// - Returns: A publisher that encodes received elements using a specified encoder,
|
||||
/// and publishes the resulting data.
|
||||
public func encode<Coder: TopLevelEncoder>(
|
||||
encoder: Coder
|
||||
) -> Publishers.Encode<Self, Coder> {
|
||||
return .init(upstream: self, encoder: encoder)
|
||||
}
|
||||
|
||||
/// Decodes the output from the upstream using a specified decoder.
|
||||
///
|
||||
/// Use `decode(type:decoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to decode data received from a `URLSession.DataTaskPublisher` or
|
||||
/// other data source using the `Decodable` protocol.
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes a JSON string. The JSON decoder
|
||||
/// parses the string, converting its fields according to the `Decodable` protocol
|
||||
/// implemented by `Article`, and successfully populating a new `Article`.
|
||||
/// The `Publishers.Decode` publisher then publishes the `Article` to the downstream.
|
||||
/// If a decoding operation fails, which happens in the case of missing or malformed
|
||||
/// data in the source JSON string, the stream terminates and passes the error to
|
||||
/// the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Data, Never>()
|
||||
/// cancellable = dataProvider
|
||||
/// .decode(type: Article.self, decoder: JSONDecoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)")},
|
||||
/// receiveValue: { print ("value: \($0)") })
|
||||
///
|
||||
/// dataProvider.send(Data("""
|
||||
/// {\"pubDate\":1574273638.575666, \
|
||||
/// \"title\" : \"My First Article\", \
|
||||
/// \"author\" : \"Gita Kumar\" }
|
||||
/// """.utf8))
|
||||
///
|
||||
/// // Prints:
|
||||
/// // ".sink() data received Article(title: "My First Article",
|
||||
/// // author: "Gita Kumar",
|
||||
/// // pubDate: 2050-11-20 18:13:58 +0000)"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - type: The encoded data to decode into a struct that conforms to
|
||||
/// the `Decodable` protocol.
|
||||
/// - decoder: A decoder that implements the `TopLevelDecoder` protocol.
|
||||
/// - Returns: A publisher that decodes a given type using a specified decoder and
|
||||
/// publishes the result.
|
||||
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
) -> 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
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let encode: (Upstream.Output) throws -> Output
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
encode: @escaping (Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.encode = encode
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
do {
|
||||
return try downstream.receive(encode(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let decode: (Upstream.Output) throws -> Output
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
decode: @escaping (Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.decode = decode
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
do {
|
||||
return try downstream.receive(decode(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
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,384 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable large_tuple
|
||||
|
||||
extension Publisher {
|
||||
/// Publishes the value of the key path.
|
||||
///
|
||||
/// In the following example, the `map(_:)` operator uses the Swift
|
||||
/// key path syntax to access the `die` member
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// the value of this `Int`,
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
/// let die: Int
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(die: Int.random(in: 1...6)))
|
||||
/// .map(\.die)
|
||||
/// .sink {
|
||||
/// print ("Rolled: \($0)")
|
||||
/// }
|
||||
/// // Prints "Rolled: 4 (or some other random value).
|
||||
///
|
||||
/// - 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
|
||||
)
|
||||
}
|
||||
/// Publishes the values of two key paths as a tuple.
|
||||
///
|
||||
/// In the following example, the `map(_:_:)` operator uses the Swift
|
||||
/// key path syntax to access the `die1` and `die2` members
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// these two values (as an `(Int, Int)` tuple),
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
/// let die1: Int
|
||||
/// let die2: Int
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(die1: Int.random(in: 1...6),
|
||||
/// die2: Int.random(in: 1...6)))
|
||||
/// .map(\.die1, \.die2)
|
||||
/// .sink { values in
|
||||
/// print("""
|
||||
/// Rolled: \(values.0), \(values.1) \
|
||||
/// (total \(values.0 + values.1))
|
||||
/// """)
|
||||
/// }
|
||||
/// // Prints "Rolled: 4, 1 (total: 5)" (or other random values).
|
||||
///
|
||||
/// - 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
|
||||
)
|
||||
}
|
||||
/// Publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// In the following example, the `map(_:_:_:)` operator uses the Swift
|
||||
/// key path syntax to access the `die1`, `die2`, and `die3` members
|
||||
/// of the `DiceRoll` structure published by the `Just` publisher.
|
||||
///
|
||||
/// The downstream sink subscriber receives only
|
||||
/// these three values (as an `(Int, Int, Int)` tuple),
|
||||
/// not the entire `DiceRoll`.
|
||||
///
|
||||
/// struct DiceRoll {
|
||||
/// let die1: Int
|
||||
/// let die2: Int
|
||||
/// let die3: Int
|
||||
/// }
|
||||
///
|
||||
/// cancellable = Just(DiceRoll(die1: Int.random(in: 1...6),
|
||||
/// die2: Int.random(in: 1...6),
|
||||
/// die3: Int.random(in: 1...6)))
|
||||
/// .map(\.die1, \.die2, \.die3)
|
||||
/// .sink { values in
|
||||
/// print("""
|
||||
/// Rolled: \(values.0), \(values.1), \(values.2) \
|
||||
/// (total \(values.0 + values.1 + values.2))
|
||||
/// """)
|
||||
/// }
|
||||
/// // Prints "Rolled: 3, 5, 4 (total: 12)" (or other random values).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`.
|
||||
/// - keyPath1: The key path of a second property on `Output`.
|
||||
/// - keyPath2: The key path of a third property on `Output`.
|
||||
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
|
||||
public func map<Result0, Result1, Result2>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
_ 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 }
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
/// A publisher that emits an output to each subscriber just once, and then finishes.
|
||||
///
|
||||
/// You can use a `Just` publisher to start a chain of publishers. A `Just` publisher
|
||||
/// is also useful when replacing a value with `Catch`.
|
||||
/// You can use a `Just` publisher to start a chain of publishers. A `Just` publisher is
|
||||
/// also useful when replacing a value with `Publishers.Catch`.
|
||||
///
|
||||
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
|
||||
/// In contrast with `Result.Publisher`, a `Just` publisher can’t fail with an error.
|
||||
/// And unlike `Optional.Publisher`, a `Just` publisher always produces a value.
|
||||
public struct Just<Output>: Publisher {
|
||||
|
||||
public typealias Failure = Never
|
||||
@@ -25,8 +26,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 +250,64 @@ 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.take() else { return }
|
||||
_ = 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
|
||||
}
|
||||
}
|
||||
@@ -28,30 +28,47 @@ extension Optional {
|
||||
self.optional = optional
|
||||
}
|
||||
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
public var publisher: Publisher {
|
||||
return Publisher(optional)
|
||||
}
|
||||
|
||||
/// The type of a Combine publisher that publishes the value of a Swift optional
|
||||
/// instance to each subscriber exactly once, if the instance has any value at
|
||||
/// all.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
/// In contrast with the `Just` publisher, which always produces a single value,
|
||||
/// this publisher might not send any values and instead finish normally,
|
||||
/// if `output` is `nil`.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
/// The kind of value published by this publisher.
|
||||
///
|
||||
/// This publisher produces the type wrapped by the optional.
|
||||
public typealias Output = Wrapped
|
||||
|
||||
/// The kind of error this publisher might publish.
|
||||
///
|
||||
/// The optional publisher never produces errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
/// The output to deliver to each subscriber.
|
||||
public let output: Wrapped?
|
||||
|
||||
/// Creates a publisher to emit the optional value of a successful result,
|
||||
/// or fail with an error.
|
||||
/// Creates a publisher to emit the value of the optional, or to finish
|
||||
/// immediately if the optional doesn't have a value.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
/// - Parameter output: The result to deliver to each subscriber.
|
||||
public init(_ output: Output?) {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
/// Implements the Publisher protocol by accepting the subscriber and
|
||||
/// immediately publishing the optional’s value if it has one, or finishing
|
||||
/// normally if it doesn’t.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to add.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
if let output = output {
|
||||
subscriber.receive(subscription: Inner(value: output,
|
||||
@@ -64,44 +81,63 @@ extension Optional {
|
||||
}
|
||||
}
|
||||
|
||||
public var ocombine: OCombine {
|
||||
return .init(self)
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
/// The type of a Combine publisher that publishes the value of a Swift optional
|
||||
/// instance to each subscriber exactly once, if the instance has any value at
|
||||
/// all.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
/// In contrast with the `Just` publisher, which always produces a single value,
|
||||
/// this publisher might not send any values and instead finish normally,
|
||||
/// if `output` is `nil`.
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
|
||||
public var publisher: Publisher {
|
||||
return Publisher(self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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.take() else { return }
|
||||
_ = 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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,11 +261,13 @@ extension Optional.OCombine.Publisher {
|
||||
in range: RangeExpression
|
||||
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
|
||||
let range = range.relative(to: 0 ..< Int.max)
|
||||
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
|
||||
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
|
||||
|
||||
// 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,217 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use the `allSatisfy(_:)` operator to determine if all elements in a stream satisfy
|
||||
/// a criteria you provide. When this publisher receives an element, it runs
|
||||
/// the predicate against the element. If the predicate returns `false`, the publisher
|
||||
/// produces a `false` value and finishes. If the upstream publisher finishes
|
||||
/// normally, this publisher produces a `true` value and finishes.
|
||||
///
|
||||
/// In the example below, the `allSatisfy(_:)` operator tests if each an integer array
|
||||
/// publisher’s elements fall into the `targetRange`:
|
||||
///
|
||||
/// let targetRange = (-1...100)
|
||||
/// let numbers = [-1, 0, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .allSatisfy { targetRange.contains($0) }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "true"
|
||||
///
|
||||
/// With operators similar to `reduce(_:_:)`, this publisher produces at most one
|
||||
/// value.
|
||||
///
|
||||
/// > Note: Upon receiving any request greater than zero, this publisher requests
|
||||
/// unlimited elements from the upstream publisher.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element.
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
/// - 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.
|
||||
///
|
||||
/// Use the `tryAllSatisfy(_:)` operator to determine if all elements in a stream
|
||||
/// satisfy a criteria in an error-throwing predicate you provide. When this publisher
|
||||
/// receives an element, it runs the predicate against the element. If the predicate
|
||||
/// returns `false`, the publisher produces a `false` value and finishes.
|
||||
/// If the upstream publisher finishes normally, this publisher produces a `true`
|
||||
/// value and finishes. If the predicate throws an error, the publisher fails and
|
||||
/// passes the error to its downstream subscriber.
|
||||
///
|
||||
/// In the example below, an error-throwing predicate tests if each of an integer
|
||||
/// array publisher’s elements fall into the `targetRange`; the predicate throws
|
||||
/// an error if an element is zero and terminates the stream.
|
||||
///
|
||||
/// let targetRange = (-1...100)
|
||||
/// let numbers = [-1, 10, 5, 0]
|
||||
///
|
||||
/// numbers.publisher
|
||||
/// .tryAllSatisfy { anInt in
|
||||
/// guard anInt != 0 else { throw RangeError() }
|
||||
/// return targetRange.contains(anInt)
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "completion: failure(RangeError())"
|
||||
///
|
||||
/// With operators similar to `reduce(_:_:)`, this publisher produces at most one
|
||||
/// value.
|
||||
///
|
||||
/// > Note: Upon receiving any request greater than zero, this publisher requests
|
||||
/// unlimited elements from the upstream publisher.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element. Return
|
||||
/// `true` to continue, or `false` to cancel the upstream and complete. The closure
|
||||
/// may throw an error, in which case the publisher cancels the upstream publisher
|
||||
/// and fails with the thrown error.
|
||||
/// - Returns: A publisher that publishes a Boolean value that indicates whether all
|
||||
/// received elements pass a given predicate.
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryAllSatisfy<Self> {
|
||||
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,158 @@
|
||||
//
|
||||
// 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 `assertNoFailure()` for internal integrity checks that are active during
|
||||
/// testing. However, it is important to note that, like its Swift counterpart
|
||||
/// `fatalError(_:)`, the `assertNoFailure()` operator asserts a fatal exception when
|
||||
/// triggered during development and testing, _and_ in shipping versions of code.
|
||||
///
|
||||
/// In the example below, a `CurrentValueSubject` publishes the initial and second
|
||||
/// values successfully. The third value, containing a `genericSubjectError`, causes
|
||||
/// the `assertNoFailure()` operator to assert a fatal exception stopping the process:
|
||||
///
|
||||
/// public enum SubjectError: Error {
|
||||
/// case genericSubjectError
|
||||
/// }
|
||||
///
|
||||
/// let subject = CurrentValueSubject<String, Error>("initial value")
|
||||
/// subject
|
||||
/// .assertNoFailure()
|
||||
/// .sink(receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0).") }
|
||||
/// )
|
||||
///
|
||||
/// subject.send("second value")
|
||||
/// subject.send(completion: .failure(SubjectError.genericSubjectError))
|
||||
///
|
||||
/// // Prints:
|
||||
/// // value: initial value.
|
||||
/// // value: second value.
|
||||
/// // The process then terminates in the debugger as the assertNoFailure
|
||||
/// // operator catches the genericSubjectError.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - prefix: A string used at the beginning of the fatal error message.
|
||||
/// - 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 integrity checks that are active during testing but
|
||||
/// don't affect 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,193 @@
|
||||
//
|
||||
// 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 `TimerPublisher` in `OpenCombineFoundation`.
|
||||
///
|
||||
/// In the following example, the `Timer.publish()` operator creates
|
||||
/// a `TimerPublisher`, which is a `ConnectablePublisher`. As a result, subscribers
|
||||
/// don’t receive any values until after a call to `connect()`.
|
||||
/// For convenience when working with a single subscriber, the `.autoconnect()`
|
||||
/// operator performs the `connect()` call when attached to by the subscriber.
|
||||
///
|
||||
/// cancellable = Timer.publish(every: 1, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .sink { date in
|
||||
/// print ("Date now: \(date)")
|
||||
/// }
|
||||
/// - 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 to an upstream connectable publisher.
|
||||
///
|
||||
/// This publisher calls `connect()` on the upstream `ConnectablePublisher` when first
|
||||
/// attached to by a subscriber.
|
||||
public class Autoconnect<Upstream: ConnectablePublisher>: Publisher {
|
||||
|
||||
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,231 @@
|
||||
//
|
||||
// Publishers.Breakpoint.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
#if !WASI
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Raises a debugger signal when a provided closure needs to stop the process in
|
||||
/// the debugger.
|
||||
///
|
||||
/// Use `breakpoint(receiveSubscription:receiveOutput:receiveCompletion:)` to examine
|
||||
/// one or more stages of the subscribe/publish/completion process and stop in
|
||||
/// the debugger, based on conditions you specify. When any of the provided closures
|
||||
/// returns `true`, this operator raises the `SIGTRAP` signal to stop the process
|
||||
/// in the debugger. Otherwise, this publisher passes through values and completions
|
||||
/// as-is.
|
||||
///
|
||||
/// In the example below, a `PassthroughSubject` publishes strings to a breakpoint
|
||||
/// republisher. When the breakpoint receives the string “`DEBUGGER`”, it returns
|
||||
/// `true`, which stops the app in the debugger.
|
||||
///
|
||||
/// let publisher = PassthroughSubject<String?, Never>()
|
||||
/// cancellable = publisher
|
||||
/// .breakpoint(
|
||||
/// receiveOutput: { value in return value == "DEBUGGER" }
|
||||
/// )
|
||||
/// .sink { print("\(String(describing: $0))" , terminator: " ") }
|
||||
///
|
||||
/// publisher.send("DEBUGGER")
|
||||
///
|
||||
/// // Prints: "error: Execution was interrupted, reason: signal SIGTRAP."
|
||||
/// // Depending on your specific environment, the console messages may
|
||||
/// // also include stack trace information, which is not shown here.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveSubscription: A closure that executes when when the publisher receives
|
||||
/// 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.
|
||||
///
|
||||
/// In this example a `PassthroughSubject` publishes strings, but its downstream
|
||||
/// `Publisher/tryMap(_:)` operator throws an error. This sends the error downstream
|
||||
/// as a `Subscribers.Completion.failure(_:)`. The `breakpointOnError()`
|
||||
/// operator receives this completion and stops the app in the debugger.
|
||||
///
|
||||
/// struct CustomError : Error {}
|
||||
/// let publisher = PassthroughSubject<String?, Error>()
|
||||
/// cancellable = publisher
|
||||
/// .tryMap { stringValue in
|
||||
/// throw CustomError()
|
||||
/// }
|
||||
/// .breakpointOnError()
|
||||
/// .sink(
|
||||
/// receiveCompletion: { completion in
|
||||
/// print("Completion: \(String(describing: completion))")
|
||||
/// },
|
||||
/// receiveValue: { aValue in
|
||||
/// print("Result: \(String(describing: aValue))")
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// publisher.send("TEST DATA")
|
||||
///
|
||||
/// // Prints: "error: Execution was interrupted, reason: signal SIGTRAP."
|
||||
/// // Depending on your specific environment, the console messages may
|
||||
/// // also include stack trace information, which is not shown here.
|
||||
///
|
||||
/// - Returns: A publisher that raises a debugger signal upon receiving a failure.
|
||||
public func breakpointOnError() -> Publishers.Breakpoint<Self> {
|
||||
return breakpoint(receiveCompletion: { 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 {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !WASI
|
||||
@@ -0,0 +1,356 @@
|
||||
//
|
||||
// Publishers.Buffer.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.01.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Buffers elements received from an upstream publisher.
|
||||
///
|
||||
/// Use `buffer(size:prefetch:whenFull:)` to collect a specific number of elements
|
||||
/// from an upstream publisher before republishing them to the downstream subscriber
|
||||
/// according to the `Publishers.BufferingStrategy` and `Publishers.PrefetchStrategy`
|
||||
/// strategy you specify.
|
||||
///
|
||||
/// If the publisher completes before reaching the `size` threshold, it buffers
|
||||
/// the elements and publishes them downstream prior to completion.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - size: The maximum number of elements to store.
|
||||
/// - prefetch: The strategy to initially populate the buffer.
|
||||
/// - whenFull: The action to take when the buffer becomes full.
|
||||
/// - Returns: A publisher that buffers elements received from an upstream publisher.
|
||||
public func buffer(
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Failure>
|
||||
) -> Publishers.Buffer<Self> {
|
||||
return .init(upstream: self,
|
||||
size: size,
|
||||
prefetch: prefetch,
|
||||
whenFull: whenFull)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A strategy for filling a buffer.
|
||||
public enum PrefetchStrategy {
|
||||
|
||||
/// A strategy to fill the buffer at subscription time, and keep it full
|
||||
/// thereafter.
|
||||
///
|
||||
/// This strategy starts by making a demand equal to the buffer’s size from
|
||||
/// the upstream when the subscriber first connects. Afterwards, it continues
|
||||
/// to demand elements from the upstream to try to keep the buffer full.
|
||||
case keepFull
|
||||
|
||||
/// A strategy that avoids prefetching and instead performs requests on demand.
|
||||
///
|
||||
/// This strategy just forwards the downstream’s requests to the upstream
|
||||
/// publisher.
|
||||
case byRequest
|
||||
}
|
||||
|
||||
/// A strategy that handles exhaustion of a buffer’s capacity.
|
||||
public enum BufferingStrategy<Failure: Error> {
|
||||
|
||||
/// When the buffer is full, discard the newly received element.
|
||||
case dropNewest
|
||||
|
||||
/// When the buffer is full, discard the oldest element in the buffer.
|
||||
case dropOldest
|
||||
|
||||
/// When the buffer is full, execute the closure to provide a custom error.
|
||||
case customError(() -> Failure)
|
||||
}
|
||||
|
||||
/// A publisher that buffers elements received from an upstream publisher.
|
||||
public struct Buffer<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The maximum number of elements to store.
|
||||
public let size: Int
|
||||
|
||||
/// The strategy for initially populating the buffer.
|
||||
public let prefetch: Publishers.PrefetchStrategy
|
||||
|
||||
/// The action to take when the buffer becomes full.
|
||||
public let whenFull: Publishers.BufferingStrategy<Failure>
|
||||
|
||||
/// Creates a publisher that buffers elements received from an upstream publisher.
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
/// - Parameter size: The maximum number of elements to store.
|
||||
/// - Parameter prefetch: The strategy for initially populating the buffer.
|
||||
/// - Parameter whenFull: The action to take when the buffer becomes full.
|
||||
public init(upstream: Upstream,
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Failure>) {
|
||||
self.upstream = upstream
|
||||
self.size = size
|
||||
self.prefetch = prefetch
|
||||
self.whenFull = whenFull
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
size: size,
|
||||
prefetch: prefetch,
|
||||
whenFull: whenFull)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.PrefetchStrategy: Equatable {}
|
||||
|
||||
extension Publishers.PrefetchStrategy: Hashable {}
|
||||
|
||||
extension Publishers.Buffer {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var recursion = false
|
||||
|
||||
private let size: Int
|
||||
|
||||
private let prefetch: Publishers.PrefetchStrategy // keepFull is 0x0
|
||||
|
||||
private let whenFull: Publishers.BufferingStrategy<Failure>
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
// TODO: Use a deque here?
|
||||
// Need to measure performance with large buffers and `dropOldest` strategy.
|
||||
private var values = [Input]()
|
||||
|
||||
private var upstreamFailed = false
|
||||
|
||||
private var terminal: Subscribers.Completion<Failure>?
|
||||
|
||||
init(downstream: Downstream,
|
||||
size: Int,
|
||||
prefetch: Publishers.PrefetchStrategy,
|
||||
whenFull: Publishers.BufferingStrategy<Failure>) {
|
||||
self.size = size
|
||||
self.prefetch = prefetch
|
||||
self.whenFull = whenFull
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
|
||||
let upstreamDemand: Subscribers.Demand
|
||||
switch prefetch {
|
||||
case .keepFull:
|
||||
upstreamDemand = .max(size)
|
||||
case .byRequest:
|
||||
upstreamDemand = .unlimited
|
||||
}
|
||||
subscription.request(upstreamDemand)
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
switch terminal {
|
||||
case nil, .finished?:
|
||||
if values.count >= size {
|
||||
switch whenFull {
|
||||
case .dropNewest:
|
||||
lock.unlock()
|
||||
return drain()
|
||||
case .dropOldest:
|
||||
values.removeFirst()
|
||||
case let .customError(makeError):
|
||||
terminal = .failure(makeError())
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
values.append(input)
|
||||
lock.unlock()
|
||||
return drain()
|
||||
case .failure?:
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state, terminal == nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
terminal = completion
|
||||
lock.unlock()
|
||||
_ = drain()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamDemand += demand
|
||||
let recursion = self.recursion
|
||||
lock.unlock()
|
||||
if recursion {
|
||||
return
|
||||
}
|
||||
|
||||
let more = drain()
|
||||
if more != .none {
|
||||
// Request the number of items just enough to fill the buffer.
|
||||
subscription.request(more)
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
values = []
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
private func drain() -> Subscribers.Demand {
|
||||
var upstreamDemand = Subscribers.Demand.none
|
||||
lock.lock()
|
||||
while true {
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return upstreamDemand
|
||||
}
|
||||
|
||||
if downstreamDemand > 0 {
|
||||
if values.isEmpty {
|
||||
if let completion = terminal {
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
return upstreamDemand
|
||||
}
|
||||
} else {
|
||||
if let completion = terminal, case .failure = completion {
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
return upstreamDemand
|
||||
}
|
||||
|
||||
let poppedValues = lockedPop(downstreamDemand)
|
||||
assert(poppedValues.count > 0,
|
||||
"""
|
||||
We check that the buffer is not empty and downstreamDemand is \
|
||||
nonzero, how can this be triggered?
|
||||
""")
|
||||
|
||||
// This should not crash because `lockedPop(_:)` returns at most
|
||||
// `downstreamDemand` items.
|
||||
downstreamDemand -= poppedValues.count
|
||||
|
||||
recursion = true
|
||||
lock.unlock()
|
||||
|
||||
var newDownstreamDemand = Subscribers.Demand.none
|
||||
var additionalUpstreamDemand = 0
|
||||
|
||||
for value in poppedValues {
|
||||
newDownstreamDemand += downstream.receive(value)
|
||||
additionalUpstreamDemand += 1
|
||||
}
|
||||
|
||||
if prefetch == .keepFull {
|
||||
upstreamDemand += additionalUpstreamDemand
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
recursion = false
|
||||
downstreamDemand += newDownstreamDemand
|
||||
}
|
||||
}
|
||||
|
||||
private func lockedPop(_ demand: Subscribers.Demand) -> [Input] {
|
||||
assert(demand > 0)
|
||||
guard let max = demand.max else {
|
||||
return values.take()
|
||||
}
|
||||
|
||||
let poppedValues = Array(values.prefix(max))
|
||||
values.removeFirst(poppedValues.count)
|
||||
return poppedValues
|
||||
}
|
||||
|
||||
var description: String { return "Buffer" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("values", values),
|
||||
("state", state),
|
||||
("downstreamDemand", downstreamDemand),
|
||||
("terminal", terminal as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.Catch.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 25.12.2019.
|
||||
//
|
||||
|
||||
%{
|
||||
instantiations = ['Catch', 'TryCatch']
|
||||
}%
|
||||
extension Publisher {
|
||||
|
||||
/// Handles errors from an upstream publisher by replacing it with another publisher.
|
||||
///
|
||||
/// Use `catch()` to replace an error from an upstream publisher with a new publisher.
|
||||
///
|
||||
/// In the example below, the `catch()` operator handles the `SimpleError` thrown by
|
||||
/// the upstream publisher by replacing the error with a `Just` publisher. This
|
||||
/// continues the stream by publishing a single value and completing normally.
|
||||
///
|
||||
/// struct SimpleError: Error {}
|
||||
/// let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryLast(where: {
|
||||
/// guard $0 != 0 else { throw SimpleError() }
|
||||
/// return true
|
||||
/// })
|
||||
/// .catch { error in
|
||||
/// Just(-1)
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
/// // Prints: -1
|
||||
///
|
||||
/// Backpressure note: This publisher passes through `request` and `cancel` to
|
||||
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
|
||||
/// demand to the new `Publisher`.
|
||||
///
|
||||
/// - SeeAlso: `replaceError`
|
||||
/// - Parameter handler: A closure that accepts the upstream failure as input and
|
||||
/// returns a publisher to replace the upstream publisher.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher.
|
||||
public func `catch`<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) -> NewPublisher
|
||||
) -> Publishers.Catch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
|
||||
/// Handles errors from an upstream publisher by either replacing it with another
|
||||
/// publisher or throwing a new error.
|
||||
///
|
||||
/// Use `tryCatch(_:)` to decide how to handle from an upstream publisher by either
|
||||
/// replacing the publisher with a new publisher, or throwing a new error.
|
||||
///
|
||||
/// In the example below, an array publisher emits values that a `tryMap(_:)` operator
|
||||
/// evaluates to ensure the values are greater than zero. If the values aren’t greater
|
||||
/// than zero, the operator throws an error to the downstream subscriber to let it
|
||||
/// know there was a problem. The subscriber, `tryCatch(_:)`, replaces the error with
|
||||
/// a new publisher using ``Just`` to publish a final value before the stream ends
|
||||
/// normally.
|
||||
///
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// var numbers = [5, 4, 3, 2, 1, -1, 7, 8, 9, 10]
|
||||
///
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMap { v in
|
||||
/// if v > 0 {
|
||||
/// return v
|
||||
/// } else {
|
||||
/// throw SimpleError.error
|
||||
/// }
|
||||
/// }
|
||||
/// .tryCatch { error in
|
||||
/// Just(0) // Send a final value before completing normally.
|
||||
/// // Alternatively, throw a new error to terminate the stream.
|
||||
/// }
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0).") },
|
||||
/// receiveValue: { print ("Received \($0).") })
|
||||
/// // Received 5.
|
||||
/// // Received 4.
|
||||
/// // Received 3.
|
||||
/// // Received 2.
|
||||
/// // Received 1.
|
||||
/// // Received 0.
|
||||
/// // Completion: finished.
|
||||
///
|
||||
/// - Parameter handler: A throwing closure that accepts the upstream failure as
|
||||
/// input. This closure can either replace the upstream publisher with a new one,
|
||||
/// or throw a new error to the downstream subscriber.
|
||||
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher, or an error.
|
||||
public func tryCatch<NewPublisher: Publisher>(
|
||||
_ handler: @escaping (Failure) throws -> NewPublisher
|
||||
) -> Publishers.TryCatch<Self, NewPublisher>
|
||||
where NewPublisher.Output == Output
|
||||
{
|
||||
return .init(upstream: self, handler: handler)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing the failed
|
||||
/// publisher with another publisher.
|
||||
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = NewPublisher.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and returns a publisher
|
||||
/// to replace the upstream publisher.
|
||||
public let handler: (Upstream.Failure) -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and returns
|
||||
/// a publisher to replace the upstream publisher.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that handles errors from an upstream publisher by replacing
|
||||
/// the failed publisher with another publisher or producing a new error.
|
||||
///
|
||||
/// Because this publisher’s handler can throw an error, `Publishers.TryCatch` defines
|
||||
/// its `Failure` type as `Error`. This is different from `Publishers.Catch`, which
|
||||
/// gets its failure type from the replacement publisher.
|
||||
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
|
||||
where Upstream.Output == NewPublisher.Output
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that accepts the upstream failure as input and either returns
|
||||
/// a publisher to replace the upstream publisher or throws an error.
|
||||
public let handler: (Upstream.Failure) throws -> NewPublisher
|
||||
|
||||
/// Creates a publisher that handles errors from an upstream publisher by
|
||||
/// replacing the failed publisher with another publisher or by throwing an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher that this publisher receives elements from.
|
||||
/// - handler: A closure that accepts the upstream failure as input and either
|
||||
/// returns a publisher to replace the upstream publisher. If this closure
|
||||
/// throws an error, the publisher terminates with the thrown error.
|
||||
public init(upstream: Upstream,
|
||||
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
|
||||
self.upstream = upstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, handler: handler)
|
||||
let uncaughtS = Inner.UncaughtS(inner: inner)
|
||||
upstream.subscribe(uncaughtS)
|
||||
}
|
||||
}
|
||||
}
|
||||
% for instantiation in instantiations:
|
||||
% throws_modifier = ' throws' if instantiation == 'TryCatch' else ''
|
||||
|
||||
extension Publishers.${instantiation} {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output,
|
||||
% if instantiation == 'Catch':
|
||||
Downstream.Failure == NewPublisher.Failure
|
||||
% else:
|
||||
Downstream.Failure == Error
|
||||
% end
|
||||
{
|
||||
struct UncaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePre(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePre(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
return inner.receivePre(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
struct CaughtS: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
typealias Input = NewPublisher.Output
|
||||
|
||||
typealias Failure = NewPublisher.Failure
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.receivePost(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.receivePost(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.receivePost(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return inner.description }
|
||||
|
||||
var customMirror: Mirror { return inner.customMirror }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case pendingPre
|
||||
case pre(Subscription)
|
||||
case pendingPost
|
||||
case post(Subscription)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var demand = Subscribers.Demand.none
|
||||
|
||||
private var state = State.pendingPre
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let handler: (Upstream.Failure)${throws_modifier} -> NewPublisher
|
||||
|
||||
init(downstream: Downstream,
|
||||
handler: @escaping (Upstream.Failure)${throws_modifier} -> NewPublisher) {
|
||||
self.downstream = downstream
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receivePre(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPre = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .pre(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
demand -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
lock.lock()
|
||||
demand += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
case .pendingPre, .pendingPost, .post, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure(let error):
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pre:
|
||||
state = .pendingPost
|
||||
lock.unlock()
|
||||
% if instantiation == 'Catch':
|
||||
handler(error).subscribe(CaughtS(inner: self))
|
||||
% else:
|
||||
do {
|
||||
try handler(error).subscribe(CaughtS(inner: self))
|
||||
} catch let anotherError {
|
||||
lock.lock()
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .failure(anotherError))
|
||||
}
|
||||
% end
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
case .pendingPre, .post, .pendingPost:
|
||||
completionBeforeSubscription()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .pendingPost = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .post(subscription)
|
||||
let demand = self.demand
|
||||
lock.unlock()
|
||||
if demand > 0 {
|
||||
subscription.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
|
||||
lock.lock()
|
||||
guard case .post = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
% if instantiation == 'Catch':
|
||||
downstream.receive(completion: completion)
|
||||
% else:
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
% end
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
switch state {
|
||||
case .pendingPre:
|
||||
// The client is only able to call the `request` method after we've sent
|
||||
// `self` downstream. We only do it in the `receivePre(subscription:)`
|
||||
// method, after setting `state` to `pre`.
|
||||
// After that `state` never becomes `pendingPre`.
|
||||
requestBeforeSubscription()
|
||||
case let .pre(subscription):
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .pendingPost:
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
case let .post(subscription):
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
case .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .pre(subscription), let .post(subscription):
|
||||
state = .cancelled
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
case .pendingPre, .pendingPost, .cancelled:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "${instantiation}" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("demand", demand)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
% end
|
||||
|
||||
private func completionBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: received completion but do not have subscription",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
private func requestBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("Unexpected state: request before subscription sent",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use `collect()` to gather elements into an array that the operator emits after
|
||||
/// the upstream publisher finishes.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
///
|
||||
/// This publisher requests an unlimited number of elements from the upstream
|
||||
/// publisher and uses an unbounded amount of memory to store the received values.
|
||||
/// The publisher may exert memory pressure on the system for very large sets of
|
||||
/// elements.
|
||||
///
|
||||
/// The `collect()` operator only sends the collected array to its downstream receiver
|
||||
/// after a request whose demand is greater than 0 items. Otherwise, `collect()` waits
|
||||
/// until it receives a non-zero request.
|
||||
///
|
||||
/// In the example below, an Integer range is a publisher that emits an array of
|
||||
/// integers:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .collect()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||
///
|
||||
/// - Returns: A publisher that collects all received items and returns them as
|
||||
/// an array upon completion.
|
||||
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,195 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use `collect(_:)` to emit arrays of at most `count` elements from an upstream
|
||||
/// publisher. If the upstream publisher finishes before collecting the specified
|
||||
/// number of elements, the publisher sends an array of only the items it received
|
||||
/// This may be fewer than `count` elements.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
///
|
||||
/// In the example below, the `collect(_:)` operator emits one partial and two full
|
||||
/// arrays based on the requested collection size of `5`:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .collect(5)
|
||||
/// .sink { print("\($0), terminator: " "") }
|
||||
///
|
||||
/// // Prints "[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [10] "
|
||||
///
|
||||
/// > Note: When this publisher receives a request for `.max(n)` elements, it requests
|
||||
/// `.max(count * n)` from the upstream publisher.
|
||||
///
|
||||
/// - Parameter count: The maximum number of received elements to buffer before
|
||||
/// 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.take()
|
||||
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.take()
|
||||
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.take() {
|
||||
buffer = []
|
||||
finished = true
|
||||
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 }
|
||||
}
|
||||
}
|
||||
@@ -5,90 +5,81 @@
|
||||
// Created by Sergej Jaskiewicz on 11.07.2019.
|
||||
//
|
||||
|
||||
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
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Calls a closure with each received element and publishes any returned
|
||||
/// optional that has a value.
|
||||
/// Calls a closure with each received element and publishes any returned optional
|
||||
/// that has a value.
|
||||
///
|
||||
/// - Parameter transform: A closure that receives a value and returns
|
||||
/// an optional value.
|
||||
/// - Returns: A publisher that republishes all non-`nil` results of calling
|
||||
/// the transform closure.
|
||||
/// OpenCombine’s `compactMap(_:)` operator performs a function similar to that of
|
||||
/// `compactMap(_:)` in the Swift standard library: the `compactMap(_:)` operator in
|
||||
/// OpenCombine removes `nil` elements in a publisher’s stream and republishes
|
||||
/// non-`nil` elements to the downstream subscriber.
|
||||
///
|
||||
/// The example below uses a range of numbers as the source for a collection based
|
||||
/// publisher. The `compactMap(_:)` operator consumes each element from the `numbers`
|
||||
/// publisher attempting to access the dictionary using the element as the key.
|
||||
/// If the example’s dictionary returns a `nil`, due to a non-existent key,
|
||||
/// `compactMap(_:)` filters out the `nil` (missing) elements.
|
||||
///
|
||||
/// let numbers = (0...5)
|
||||
/// let romanNumeralDict: [Int : String] =
|
||||
/// [1: "I", 2: "II", 3: "III", 5: "V"]
|
||||
///
|
||||
/// cancellable = numbers.publisher
|
||||
/// .compactMap { romanNumeralDict[$0] }
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "I II III V"
|
||||
///
|
||||
/// - Parameter transform: A closure that receives a value and returns an optional
|
||||
/// value.
|
||||
/// - Returns: Any non-`nil` optional results of the calling the supplied closure.
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult?
|
||||
) -> Publishers.CompactMap<Self, ElementOfResult> {
|
||||
return .init(upstream: self, transform: transform)
|
||||
}
|
||||
|
||||
/// Calls an error-throwing closure with each received element and publishes
|
||||
/// any returned optional that has a value.
|
||||
/// Calls an error-throwing closure with each received element and publishes any
|
||||
/// returned optional that has a value.
|
||||
///
|
||||
/// If the closure throws an error, the publisher cancels the upstream and sends
|
||||
/// the thrown error to the downstream receiver as a `Failure`.
|
||||
/// Use `tryCompactMap(_:)` to remove `nil` elements from a publisher’s stream based
|
||||
/// on an error-throwing closure you provide. If the closure throws an error,
|
||||
/// the publisher cancels the upstream publisher and sends the thrown error to
|
||||
/// the downstream subscriber as a `Publisher.Failure`.
|
||||
///
|
||||
/// - Parameter transform: an error-throwing closure that receives a value
|
||||
/// and returns an optional value.
|
||||
/// - Returns: A publisher that republishes all non-`nil` results of calling
|
||||
/// the `transform` closure.
|
||||
/// The following example uses an array of numbers as the source for
|
||||
/// a collection-based publisher. A `tryCompactMap(_:)` operator consumes each integer
|
||||
/// from the publisher and uses a dictionary to transform the numbers from its Arabic
|
||||
/// to Roman numerals, as an optional `String`.
|
||||
///
|
||||
/// If the closure called by `tryCompactMap(_:)` fails to look up a Roman numeral,
|
||||
/// it returns the optional String `(unknown)`.
|
||||
///
|
||||
/// If the closure called by `tryCompactMap(_:)` determines the input is `0`, it
|
||||
/// throws an error. The `tryCompactMap(_:)` operator catches this error and stops
|
||||
/// publishing, sending a `Subscribers.Completion.failure(_:)` that wraps the error.
|
||||
///
|
||||
/// struct ParseError: Error {}
|
||||
/// func romanNumeral(from: Int) throws -> String? {
|
||||
/// let romanNumeralDict: [Int : String] =
|
||||
/// [1: "I", 2: "II", 3: "III", 4: "IV", 5: "V"]
|
||||
/// guard from != 0 else { throw ParseError() }
|
||||
/// return romanNumeralDict[from]
|
||||
/// }
|
||||
/// let numbers = [6, 5, 4, 3, 2, 1, 0]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryCompactMap { try romanNumeral(from: $0) }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("\($0)") },
|
||||
/// receiveValue: { print ("\($0)", terminator: " ") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "(Unknown) V IV III II I failure(ParseError())"
|
||||
///
|
||||
/// - Parameter transform: An error-throwing closure that receives a value and returns
|
||||
/// an optional value.
|
||||
/// - Returns: Any non-`nil` optional results of calling the supplied closure.
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
|
||||
@@ -123,89 +114,128 @@ extension Publishers.TryCompactMap {
|
||||
}
|
||||
}
|
||||
|
||||
private class _CompactMap<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscription
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Transform = (Input) -> Result<Downstream.Input?, Downstream.Failure>
|
||||
extension Publishers {
|
||||
|
||||
fileprivate var _transform: Transform?
|
||||
/// A publisher that republishes all non-`nil` results of calling a closure
|
||||
/// with each received element.
|
||||
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
var _isCompleted: Bool {
|
||||
return _transform == nil
|
||||
}
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
init(downstream: Downstream, transform: @escaping Transform) {
|
||||
_transform = transform
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
/// A closure that receives values from the upstream publisher
|
||||
/// and returns optional values.
|
||||
public let transform: (Upstream.Output) -> Output?
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
guard let transform = _transform else { return .none }
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) -> Output?) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
switch transform(input) {
|
||||
case .success(let output?):
|
||||
return downstream.receive(output)
|
||||
case .success(nil):
|
||||
return .max(1)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
_transform = nil
|
||||
return .none
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard !_isCompleted else { return }
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
/// 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 {
|
||||
|
||||
override func cancel() {
|
||||
_transform = nil
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
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>
|
||||
: _CompactMap<Upstream, Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Downstream.Failure == Upstream.Failure
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
private let filter: (Input) -> Downstream.Input?
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
init(downstream: Downstream, filter: @escaping (Input) -> Downstream.Input?) {
|
||||
self.downstream = downstream
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
if let output = filter(input) {
|
||||
return downstream.receive(output)
|
||||
}
|
||||
return .max(1)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "CompactMap" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if !_isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCompactMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _CompactMap<Upstream, Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Downstream.Failure == Error
|
||||
: FilterProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Output?>
|
||||
where Downstream.Failure == Error, Downstream.Input == Output
|
||||
{
|
||||
var description: String { return "TryCompactMap" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if !_isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
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,371 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use `min()` to find the minimum value in a stream of elements from
|
||||
/// an upstream publisher.
|
||||
///
|
||||
/// In the example below, the `min()` operator emits a value when the publisher
|
||||
/// finishes, that value is the minimum of the values received from upstream, which
|
||||
/// is `-1`.
|
||||
///
|
||||
/// let numbers = [-1, 0, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .min()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "-1"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - 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.
|
||||
///
|
||||
/// Use `max()` to determine the maximum value in the stream of elements from
|
||||
/// an upstream publisher.
|
||||
///
|
||||
/// In the example below, the `max()` operator emits a value when the publisher
|
||||
/// finishes, that value is the maximum of the values received from upstream, which
|
||||
/// is `10`.
|
||||
///
|
||||
/// let numbers = [0, 10, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .max()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "10"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - 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.
|
||||
///
|
||||
/// Use `min(by:)` to determine the minimum value in the stream of elements from
|
||||
/// an upstream publisher using a comparison operation you specify.
|
||||
///
|
||||
/// This operator is useful when the value received from the upstream publisher isn’t
|
||||
/// `Comparable`.
|
||||
///
|
||||
/// In the example below an array publishes enumeration elements representing playing
|
||||
/// card ranks. The `min(by:)` operator compares the current and next elements using
|
||||
/// the `rawValue` property of each enumeration value in the user supplied closure and
|
||||
/// prints the minimum value found after publishing all of the elements.
|
||||
///
|
||||
/// enum Rank: Int {
|
||||
/// case ace = 1, two, three, four, five, six, seven, eight, nine,
|
||||
/// ten, jack, queen, king
|
||||
/// }
|
||||
///
|
||||
/// let cards: [Rank] = [.five, .queen, .ace, .eight, .king]
|
||||
/// cancellable = cards.publisher
|
||||
/// .min {
|
||||
/// return $0.rawValue < $1.rawValue
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "ace"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they’re in increasing order.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min(
|
||||
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.
|
||||
///
|
||||
/// Use `tryMin(by:)` to determine the minimum value of elements received from
|
||||
/// the upstream publisher using an error-throwing closure you specify.
|
||||
///
|
||||
/// In the example below, an array publishes elements. The `tryMin(by:)` operator
|
||||
/// executes the error-throwing closure that throws when the `first` element is an odd
|
||||
/// number, terminating the publisher.
|
||||
///
|
||||
/// struct IllegalValueError: Error {}
|
||||
///
|
||||
/// let numbers: [Int] = [0, 10, 6, 13, 22, 22]
|
||||
/// numbers.publisher
|
||||
/// .tryMin { first, second -> Bool in
|
||||
/// if (first % 2 != 0) {
|
||||
/// throw IllegalValueError()
|
||||
/// }
|
||||
/// return first < second
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "completion: failure(IllegalValueError())"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they’re in increasing order. If this closure throws,
|
||||
/// the publisher terminates with a `Subscribers.Completion.failure(_:)`.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMin(
|
||||
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.
|
||||
///
|
||||
/// Use `max(by:)` to determine the maximum value of elements received from
|
||||
/// the upstream publisher based on an ordering closure you specify.
|
||||
///
|
||||
/// In the example below, an array publishes enumeration elements representing playing
|
||||
/// card ranks. The `max(by:)` operator compares the current and next elements using
|
||||
/// the `rawValue` property of each enumeration value in the user supplied closure and
|
||||
/// prints the maximum value found after publishing all of the elements.
|
||||
///
|
||||
/// enum Rank: Int {
|
||||
/// case ace = 1, two, three, four, five, six, seven, eight, nine,
|
||||
/// ten, jack, queen, king
|
||||
/// }
|
||||
///
|
||||
/// let cards: [Rank] = [.five, .queen, .ace, .eight, .jack]
|
||||
/// cancellable = cards.publisher
|
||||
/// .max {
|
||||
/// return $0.rawValue > $1.rawValue
|
||||
/// }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "queen"
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they’re in increasing order.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max(
|
||||
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.
|
||||
///
|
||||
/// Use `tryMax(by:)` to determine the maximum value of elements received from
|
||||
/// the upstream publisher using an error-throwing closure you specify.
|
||||
///
|
||||
/// In the example below, an array publishes elements. The `tryMax(by:)` operator
|
||||
/// executes the error-throwing closure that throws when the `first` element is
|
||||
/// an odd number, terminating the publisher.
|
||||
///
|
||||
/// struct IllegalValueError: Error {}
|
||||
///
|
||||
/// let numbers: [Int] = [0, 10, 6, 13, 22, 22]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryMax { first, second -> Bool in
|
||||
/// if (first % 2 != 0) {
|
||||
/// throw IllegalValueError()
|
||||
/// }
|
||||
/// return first > second
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: completion: failure(IllegalValueError())
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they’re in increasing order. If this closure throws,
|
||||
/// the publisher terminates with a ``Subscribers/Completion/failure(_:)``.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
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,474 @@
|
||||
//
|
||||
// Publishers.Concatenate.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 24.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Prefixes a publisher’s output with the specified values.
|
||||
///
|
||||
/// Use `prepend(_:)` when you need to prepend specific elements before the output
|
||||
/// of a publisher.
|
||||
///
|
||||
/// In the example below, the `prepend(_:)` operator publishes the provided elements
|
||||
/// before republishing all elements from `dataElements`:
|
||||
///
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .prepend(0, 1, 255)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
|
||||
///
|
||||
/// - Parameter elements: The elements to publish before this publisher’s elements.
|
||||
/// - Returns: A publisher that prefixes the specified elements prior to this
|
||||
/// 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.
|
||||
///
|
||||
/// Use `prepend(_:)` to publish values from two publishers when you need to prepend
|
||||
/// one publisher’s elements to another.
|
||||
///
|
||||
/// In this example the `/prepend(_:)-v9sb` operator publishes the provided sequence
|
||||
/// before republishing all elements from `dataElements`:
|
||||
///
|
||||
/// let prefixValues = [0, 1, 255]
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .prepend(prefixValues)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
|
||||
///
|
||||
/// - Parameter elements: A sequence of elements to publish before this publisher’s
|
||||
/// elements.
|
||||
/// - 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 the output of this publisher with the elements emitted by the given
|
||||
/// publisher.
|
||||
///
|
||||
/// Use `prepend(_:)` to publish values from two publishers when you need to prepend
|
||||
/// one publisher’s elements to another.
|
||||
///
|
||||
/// In the example below, a publisher of `prefixValues` publishes its elements before
|
||||
/// the `dataElements` publishes its elements:
|
||||
///
|
||||
/// let prefixValues = [0, 1, 255]
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .prepend(prefixValues.publisher)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
|
||||
///
|
||||
/// - Parameter publisher: The prefixing publisher.
|
||||
/// - Returns: A publisher that prefixes the prefixing publisher’s elements prior to
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Appends a publisher’s output with the specified elements.
|
||||
///
|
||||
/// Use `append(_:)` when you need to prepend specific elements after the output of
|
||||
/// a publisher.
|
||||
///
|
||||
/// In the example below, the `append(_:)` operator publishes the provided elements
|
||||
/// after republishing all elements from `dataElements`:
|
||||
///
|
||||
/// let dataElements = (0...10)
|
||||
/// cancellable = dataElements.publisher
|
||||
/// .append(0, 1, 255)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4 5 6 7 8 9 10 0 1 255"
|
||||
///
|
||||
///
|
||||
/// - Parameter elements: Elements to publish after this publisher’s elements.
|
||||
/// - Returns: A publisher that appends the specifiecd elements after this publisher’s
|
||||
/// elements.
|
||||
public func append(
|
||||
_ elements: Output...
|
||||
) -> Publishers.Concatenate<Self, Publishers.Sequence<[Output], Failure>> {
|
||||
return append(elements)
|
||||
}
|
||||
|
||||
/// Appends a publisher’s output with the specified sequence.
|
||||
///
|
||||
/// Use `append(_:)` to append a sequence to the end of
|
||||
/// a publisher’s output.
|
||||
///
|
||||
/// In the example below, the `append(_:)` publisher republishes all elements from
|
||||
/// `groundTransport` until it finishes, then publishes the members of `airTransport`:
|
||||
///
|
||||
/// let groundTransport = ["car", "bus", "truck", "subway", "bicycle"]
|
||||
/// let airTransport = ["parasail", "jet", "helicopter", "rocket"]
|
||||
/// cancellable = groundTransport.publisher
|
||||
/// .append(airTransport)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "car bus truck subway bicycle parasail jet helicopter rocket"
|
||||
///
|
||||
/// - Parameter elements: A sequence of elements to publish after this publisher’s
|
||||
/// elements.
|
||||
/// - Returns: A publisher that appends the sequence of elements after this
|
||||
/// publisher’s elements.
|
||||
public func append<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Concatenate<Self, Publishers.Sequence<Elements, Failure>>
|
||||
where Output == Elements.Element
|
||||
{
|
||||
return append(.init(sequence: elements))
|
||||
}
|
||||
|
||||
/// Appends the output of this publisher with the elements emitted by the given
|
||||
/// publisher.
|
||||
///
|
||||
/// Use `append(_:)` to append the output of one publisher to another.
|
||||
/// The `append(_:)` operator produces no elements until this publisher finishes.
|
||||
/// It then produces this publisher’s elements, followed by the given publisher’s
|
||||
/// elements. If this publisher fails with an error, the given publishers elements
|
||||
/// aren’t published.
|
||||
///
|
||||
/// In the example below, the `append` publisher republishes all elements from
|
||||
/// the `numbers` publisher until it finishes, then publishes all elements from
|
||||
/// the `otherNumbers` publisher:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// let otherNumbers = (25...35)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .append(otherNumbers.publisher)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "0 1 2 3 4 5 6 7 8 9 10 25 26 27 28 29 30 31 32 33 34 35 "
|
||||
///
|
||||
/// - Parameter publisher: The appending publisher.
|
||||
/// - Returns: A publisher that appends the appending publisher’s elements after this
|
||||
/// 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 another
|
||||
/// 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<Downstream>.PrefixSubscriber(inner: inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate: Equatable where Prefix: Equatable, Suffix: Equatable {}
|
||||
|
||||
extension Publishers.Concatenate {
|
||||
fileprivate final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Suffix.Output, Downstream.Failure == Suffix.Failure
|
||||
{
|
||||
typealias Input = Suffix.Output
|
||||
|
||||
typealias Failure = Suffix.Failure
|
||||
|
||||
fileprivate struct PrefixSubscriber {
|
||||
let inner: Inner<Downstream>
|
||||
}
|
||||
|
||||
fileprivate struct SuffixSubscriber {
|
||||
let inner: Inner<Downstream>
|
||||
}
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private var prefixState = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var suffixState = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var suffix: Suffix?
|
||||
|
||||
private var pending = Subscribers.Demand.none
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, suffix: Suffix) {
|
||||
self.downstream = downstream
|
||||
self.suffix = suffix
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
pending += demand
|
||||
guard let subscription = prefixState.subscription ?? suffixState.subscription
|
||||
else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let upstreamSubscription =
|
||||
prefixState.subscription ?? suffixState.subscription
|
||||
prefixState = .terminal
|
||||
suffixState = .terminal
|
||||
|
||||
// We MUST release the object AFTER unlocking the lock,
|
||||
// since releasing it may trigger execution of arbitrary code,
|
||||
// for example, if the object has a deinit.
|
||||
// When the object deallocates, its deinit is called, and holding
|
||||
// the lock at that moment can lead to deadlocks.
|
||||
|
||||
withExtendedLifetime(suffix) {
|
||||
suffix = nil
|
||||
lock.unlock()
|
||||
upstreamSubscription?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "Concatenate" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func prefixReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = prefixState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
prefixState = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
private func prefixReceive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = prefixState, pending != .none else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
pending -= 1
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
if newDemand == .none {
|
||||
return .none
|
||||
}
|
||||
lock.lock()
|
||||
pending += newDemand
|
||||
lock.unlock()
|
||||
return newDemand
|
||||
}
|
||||
|
||||
private func prefixReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = prefixState else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
prefixState = .terminal
|
||||
lock.unlock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
suffix?.subscribe(SuffixSubscriber(inner: self))
|
||||
case .failure:
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func suffixReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = suffixState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
suffixState = .subscribed(subscription)
|
||||
let pending = self.pending
|
||||
lock.unlock()
|
||||
if pending != .none {
|
||||
subscription.request(pending)
|
||||
}
|
||||
}
|
||||
|
||||
private func suffixReceive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = suffixState else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
private func suffixReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = suffixState else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
prefixState = .terminal
|
||||
suffixState = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PrefixSubscriber conformances
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber: Subscriber {
|
||||
|
||||
fileprivate typealias Input = Suffix.Output
|
||||
|
||||
fileprivate typealias Failure = Suffix.Failure
|
||||
|
||||
fileprivate var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
inner.prefixReceive(subscription: subscription)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.prefixReceive(input)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.prefixReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber
|
||||
: CustomStringConvertible
|
||||
{
|
||||
fileprivate var description: String {
|
||||
return inner.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber
|
||||
: CustomReflectable
|
||||
{
|
||||
fileprivate var customMirror: Mirror {
|
||||
return inner.customMirror
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.PrefixSubscriber
|
||||
: CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
fileprivate var playgroundDescription: Any {
|
||||
return inner.playgroundDescription
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SuffixSubscriber conformances
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber: Subscriber {
|
||||
|
||||
fileprivate typealias Input = Suffix.Output
|
||||
|
||||
fileprivate typealias Failure = Suffix.Failure
|
||||
|
||||
fileprivate var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
inner.suffixReceive(subscription: subscription)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return inner.suffixReceive(input)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
inner.suffixReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber
|
||||
: CustomStringConvertible
|
||||
{
|
||||
fileprivate var description: String {
|
||||
return inner.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber
|
||||
: CustomReflectable
|
||||
{
|
||||
fileprivate var customMirror: Mirror {
|
||||
return inner.customMirror
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Concatenate.Inner.SuffixSubscriber
|
||||
: CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
fileprivate var playgroundDescription: Any {
|
||||
return inner.playgroundDescription
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use `contains(_:)` to find the first element in an upstream that’s equal to
|
||||
/// the supplied argument. The `Publishers.Contains` publisher consumes all received
|
||||
/// elements until the upstream publisher produces a matching element. Upon finding
|
||||
/// the first match, it emits `true` and finishes normally. If the upstream finishes
|
||||
/// normally without producing a matching element, this publisher emits `false` and
|
||||
/// finishes.
|
||||
///
|
||||
/// In the example below, the `contains(_:)` operator emits `true` the first time it
|
||||
/// receives the value `5` from the `numbers.publisher`, and then finishes normally.
|
||||
///
|
||||
/// let numbers = [-1, 5, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .contains(5)
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "true"
|
||||
///
|
||||
/// - Parameter output: An element to match against.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// 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.
|
||||
///
|
||||
/// Use `contains(where:)` to find the first element in an upstream that satisfies
|
||||
/// the closure you provide. This operator consumes elements produced from
|
||||
/// the upstream publisher until the upstream publisher produces a matching element.
|
||||
///
|
||||
/// This operator is useful when the upstream publisher produces elements that don’t
|
||||
/// conform to `Equatable`.
|
||||
///
|
||||
/// In the example below, the `contains(where:)` operator tests elements against
|
||||
/// the supplied closure and emits `true` for the first elements that’s greater than
|
||||
/// `4`, and then finishes normally.
|
||||
///
|
||||
/// let numbers = [-1, 0, 10, 5]
|
||||
/// numbers.publisher
|
||||
/// .contains {$0 > 4}
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "true"
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value that indicates whether the element satisfies
|
||||
/// the closure’s comparison logic.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func contains(
|
||||
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.
|
||||
///
|
||||
/// Use `tryContains(where:)` to find the first element in an upstream that satisfies
|
||||
/// the error-throwing closure you provide.
|
||||
///
|
||||
/// This operator consumes elements produced from the upstream publisher until
|
||||
/// the upstream publisher either:
|
||||
///
|
||||
/// - Produces a matching element, after which it emits `true` and the publisher
|
||||
/// finishes normally.
|
||||
/// - Emits `false` if no matching element is found and the publisher finishes
|
||||
/// normally.
|
||||
///
|
||||
/// If the predicate throws an error, the publisher fails, passing the error to its
|
||||
/// downstream.
|
||||
///
|
||||
/// In the example below, the `tryContains(where:)` operator tests values to find
|
||||
/// an element less than `10`; when the closure finds an odd number, like `3`,
|
||||
/// the publisher terminates with an `IllegalValueError`.
|
||||
///
|
||||
/// struct IllegalValueError: Error {}
|
||||
///
|
||||
/// let numbers = [3, 2, 10, 5, 0, 9]
|
||||
/// numbers.publisher
|
||||
/// .tryContains {
|
||||
/// if ($0 % 2 != 0) {
|
||||
/// throw IllegalValueError()
|
||||
/// }
|
||||
/// return $0 < 10
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "completion: failure(IllegalValueError())"
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value that indicates whether the element satisfies
|
||||
/// the closure’s comparison logic.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func tryContains(
|
||||
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,27 @@
|
||||
// Created by Joseph Spadafora on 6/25/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// Use `count(`` to determine the number of elements received from the upstream
|
||||
/// publisher before it completes:
|
||||
///
|
||||
/// let numbers = (0...10)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .count()
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "11"
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self> {
|
||||
return Publishers.Count(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the number of elements received
|
||||
@@ -33,62 +54,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" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
//
|
||||
// Publishers.Debounce.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.12.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes elements only after a specified time interval elapses between events.
|
||||
///
|
||||
/// Use the `debounce(for:scheduler:options:)` operator to control the number of
|
||||
/// values and time between delivery of values from the upstream publisher. This
|
||||
/// operator is useful to process bursty or high-volume event streams where you need
|
||||
/// to reduce the number of values delivered to the downstream to a rate you specify.
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes elements on a schedule defined
|
||||
/// by the `bounces` array. The array is composed of tuples representing a value sent
|
||||
/// by the `PassthroughSubject`, and a `TimeInterval` ranging from one-quarter second
|
||||
/// up to 2 seconds that drives a delivery timer. As the queue builds, elements
|
||||
/// arriving faster than one-half second `debounceInterval` are discarded, while
|
||||
/// elements arriving at a rate slower than `debounceInterval` are passed through to
|
||||
/// the `sink(receiveValue:)` operator.
|
||||
///
|
||||
/// let bounces:[(Int,TimeInterval)] = [
|
||||
/// (0, 0),
|
||||
/// (1, 0.25), // 0.25s interval since last index
|
||||
/// (2, 1), // 0.75s interval since last index
|
||||
/// (3, 1.25), // 0.25s interval since last index
|
||||
/// (4, 1.5), // 0.25s interval since last index
|
||||
/// (5, 2) // 0.5s interval since last index
|
||||
/// ]
|
||||
///
|
||||
/// let subject = PassthroughSubject<Int, Never>()
|
||||
/// cancellable = subject
|
||||
/// .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
|
||||
/// .sink { index in
|
||||
/// print ("Received index \(index)")
|
||||
/// }
|
||||
///
|
||||
/// for bounce in bounces {
|
||||
/// DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
|
||||
/// subject.send(bounce.0)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Received index 1
|
||||
/// // Received index 4
|
||||
/// // Received index 5
|
||||
///
|
||||
/// // Here is the event flow shown from the perspective of time, showing value
|
||||
/// // delivery through the `debounce()` operator:
|
||||
///
|
||||
/// // Time 0: Send index 0.
|
||||
/// // Time 0.25: Send index 1. Index 0 was waiting and is discarded.
|
||||
/// // Time 0.75: Debounce period ends, publish index 1.
|
||||
/// // Time 1: Send index 2.
|
||||
/// // Time 1.25: Send index 3. Index 2 was waiting and is discarded.
|
||||
/// // Time 1.5: Send index 4. Index 3 was waiting and is discarded.
|
||||
/// // Time 2: Debounce period ends, publish index 4. Also, send index 5.
|
||||
/// // Time 2.5: Debounce period ends, publish index 5.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - dueTime: The time the publisher should wait before publishing an element.
|
||||
/// - scheduler: The scheduler on which this publisher delivers elements
|
||||
/// - options: Scheduler options that customize this publisher’s delivery
|
||||
/// of elements.
|
||||
/// - Returns: A publisher that publishes events only after a specified time elapses.
|
||||
public func debounce<Context: Scheduler>(
|
||||
for dueTime: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions? = nil
|
||||
) -> Publishers.Debounce<Self, Context> {
|
||||
return .init(upstream: self,
|
||||
dueTime: dueTime,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes elements only after a specified time interval elapses
|
||||
/// between events.
|
||||
public struct Debounce<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The amount of time the publisher should wait before publishing an element.
|
||||
public let dueTime: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler on which this publisher delivers elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// Scheduler options that customize this publisher’s delivery of elements.
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
dueTime: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.upstream = upstream
|
||||
self.dueTime = dueTime
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
dueTime: dueTime,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Debounce {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private typealias Generation = UInt64
|
||||
|
||||
private enum CancellerState {
|
||||
case pending
|
||||
case active(Cancellable)
|
||||
|
||||
fileprivate func cancel() {
|
||||
if case let .active(cancellable) = self {
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let dueTime: Context.SchedulerTimeType.Stride
|
||||
|
||||
private let scheduler: Context
|
||||
|
||||
private let options: Context.SchedulerOptions?
|
||||
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private var currentCancellers = [Generation : CancellerState]()
|
||||
|
||||
private var currentValue: Output?
|
||||
|
||||
private var currentGeneration: Generation = 0
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
init(downstream: Downstream,
|
||||
dueTime: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.downstream = downstream
|
||||
self.dueTime = dueTime
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
currentGeneration += 1
|
||||
let generation = currentGeneration
|
||||
currentValue = input
|
||||
let due = scheduler.now.advanced(by: dueTime)
|
||||
let previousCancellers = self.currentCancellers.take()
|
||||
currentCancellers[generation] = .pending
|
||||
lock.unlock()
|
||||
let newCanceller = scheduler.schedule(after: due,
|
||||
interval: dueTime,
|
||||
tolerance: scheduler.minimumTolerance,
|
||||
options: options) {
|
||||
self.due(generation: generation)
|
||||
}
|
||||
lock.lock()
|
||||
currentCancellers[generation] = .active(newCanceller)
|
||||
lock.unlock()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let previousCancellers = currentCancellers.take()
|
||||
lock.unlock()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
}
|
||||
scheduler.schedule {
|
||||
self.downstreamLock.lock()
|
||||
self.downstream.receive(completion: completion)
|
||||
self.downstreamLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamDemand += demand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case .subscribed(let subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let previousCancellers = currentCancellers.take()
|
||||
lock.unlock()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
}
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Debounce" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("downstreamDemand", downstreamDemand),
|
||||
("currentValue", currentValue as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
private func due(generation: Generation) {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// If this condition holds, it means that no values were received
|
||||
// in this time frame => we should propagate the current value downstream.
|
||||
guard generation == currentGeneration, let value = currentValue else {
|
||||
let canceller = currentCancellers[generation]
|
||||
lock.unlock()
|
||||
canceller?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
guard let canceller = currentCancellers[generation].take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
let hasAnyDemand = downstreamDemand != .none
|
||||
if hasAnyDemand {
|
||||
downstreamDemand -= 1
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
canceller.cancel()
|
||||
|
||||
guard hasAnyDemand else { return }
|
||||
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(value)
|
||||
downstreamLock.unlock()
|
||||
|
||||
if newDemand == .none { return }
|
||||
|
||||
lock.lock()
|
||||
downstreamDemand += newDemand
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,269 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use `delay(for:tolerance:scheduler:options:)` when you need to delay the delivery
|
||||
/// of elements to a downstream by a specified amount of time.
|
||||
///
|
||||
/// In this example, a `Timer` publishes an event every second.
|
||||
/// The `delay(for:tolerance:scheduler:options:)` operator holds the delivery of
|
||||
/// the initial element for 3 seconds (±0.5 seconds), after which each element is
|
||||
/// delivered to the downstream on the main run loop after the specified delay:
|
||||
///
|
||||
/// let df = DateFormatter()
|
||||
/// df.dateStyle = .none
|
||||
/// df.timeStyle = .long
|
||||
/// cancellable = Timer.publish(every: 1.0, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .handleEvents(receiveOutput: { date in
|
||||
/// print ("Sending Timestamp \'\(df.string(from: date))\' to delay()")
|
||||
/// })
|
||||
/// .delay(for: .seconds(3), scheduler: RunLoop.main, options: .none)
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)", terminator: "\n") },
|
||||
/// receiveValue: { value in
|
||||
/// let now = Date()
|
||||
/// print("""
|
||||
/// At \(df.string(from: now)) received Timestamp \
|
||||
/// \'\(df.string(from: value))\' \
|
||||
/// sent: \(String(format: "%.2f", now.timeIntervalSince(value)))
|
||||
/// secs ago
|
||||
/// """)
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Sending Timestamp '5:02:33 PM PDT' to delay()
|
||||
/// // Sending Timestamp '5:02:34 PM PDT' to delay()
|
||||
/// // Sending Timestamp '5:02:35 PM PDT' to delay()
|
||||
/// // Sending Timestamp '5:02:36 PM PDT' to delay()
|
||||
/// // At 5:02:36 PM PDT received Timestamp '5:02:33 PM PDT' sent: 3.00
|
||||
/// // secs ago
|
||||
/// // Sending Timestamp '5:02:37 PM PDT' to delay()
|
||||
/// // At 5:02:37 PM PDT received Timestamp '5:02:34 PM PDT' sent: 3.00
|
||||
/// // secs ago
|
||||
/// // Sending Timestamp '5:02:38 PM PDT' to delay()
|
||||
/// // At 5:02:38 PM PDT received Timestamp '5:02:35 PM PDT' sent: 3.00
|
||||
/// // secs ago
|
||||
///
|
||||
/// The delay affects the delivery of elements and completion, but not of the original
|
||||
/// subscription.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The amount of time to delay.
|
||||
/// - tolerance: The allowed tolerance in firing delayed events.
|
||||
/// - scheduler: The scheduler to deliver the delayed events.
|
||||
/// - options: Options relevant to the scheduler’s behavior.
|
||||
/// - Returns: A publisher that delays delivery of elements and completion to
|
||||
/// the downstream receiver.
|
||||
public func delay<Context: Scheduler>(
|
||||
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
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
scheduler: scheduler,
|
||||
options: options)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Delay {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let downstream: Downstream
|
||||
private let interval: Context.SchedulerTimeType.Stride
|
||||
private let tolerance: Context.SchedulerTimeType.Stride
|
||||
private let scheduler: Context
|
||||
private let options: Context.SchedulerOptions?
|
||||
private var state = SubscriptionStatus.awaitingSubscription
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
tolerance: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
options: Context.SchedulerOptions?) {
|
||||
self.downstream = downstream
|
||||
self.interval = interval
|
||||
self.tolerance = tolerance
|
||||
self.scheduler = scheduler
|
||||
self.options = options
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
private func schedule(_ work: @escaping () -> Void) {
|
||||
scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: interval),
|
||||
tolerance: tolerance,
|
||||
options: options,
|
||||
work)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
state = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
schedule {
|
||||
self.scheduledReceive(input)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
private func scheduledReceive(_ input: Input) {
|
||||
lock.lock()
|
||||
guard state.subscription != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
let newDemand = downstream.receive(input)
|
||||
downstreamLock.unlock()
|
||||
if newDemand == .none { return }
|
||||
lock.lock()
|
||||
let subscription = state.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(newDemand)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .pendingTerminal(subscription)
|
||||
lock.unlock()
|
||||
schedule {
|
||||
self.scheduledReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledReceive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .pendingTerminal = state else {
|
||||
assertionFailure(
|
||||
"This branch should not be reachable! Please report a bug."
|
||||
)
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
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,155 @@
|
||||
//
|
||||
// Publishers.Drop.swift
|
||||
//
|
||||
//
|
||||
// Created by Sven Weidauer on 03.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
/// Omits the specified number of elements before republishing subsequent elements.
|
||||
///
|
||||
/// Use `dropFirst(_:)` when you want to drop the first `n` elements from the upstream
|
||||
/// publisher, and republish the remaining elements.
|
||||
///
|
||||
/// The example below drops the first five elements from the stream:
|
||||
///
|
||||
/// let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .dropFirst(5)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// // Prints: "6 7 8 9 10 "
|
||||
///
|
||||
/// - Parameter count: The number of elements to omit. The default is `1`.
|
||||
/// - Returns: A publisher that doesn’t republish the first `count` elements.
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self> {
|
||||
return .init(upstream: self, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(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
|
||||
{
|
||||
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 != .none {
|
||||
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>) {
|
||||
lock.lock()
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
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() {
|
||||
lock.lock()
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Drop" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
//
|
||||
// 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.
|
||||
///
|
||||
/// Use `drop(untilOutputFrom:)` to ignore elements from the upstream publisher until
|
||||
/// another, second, publisher delivers its first element.
|
||||
/// This publisher requests a single value from the second publisher, and it ignores
|
||||
/// (drops) all elements from the upstream publisher until the second publisher
|
||||
/// produces a value. After the second publisher produces an element,
|
||||
/// `drop(untilOutputFrom:)` cancels its subscription to the second publisher, and
|
||||
/// allows events from the upstream publisher to pass through.
|
||||
///
|
||||
/// After this publisher receives a subscription from the upstream publisher, it
|
||||
/// passes through backpressure requests from downstream to the upstream publisher.
|
||||
/// If the upstream publisher acts on those requests before the other publisher
|
||||
/// produces an item, this publisher drops the elements it receives from the upstream
|
||||
/// publisher.
|
||||
///
|
||||
/// In the example below, the `pub1` publisher defers publishing its elements until
|
||||
/// the `pub2` publisher delivers its first element:
|
||||
///
|
||||
/// let upstream = PassthroughSubject<Int, Never>()
|
||||
/// let second = PassthroughSubject<String, Never>()
|
||||
/// cancellable = upstream
|
||||
/// .drop(untilOutputFrom: second)
|
||||
/// .sink { print("\($0)", terminator: " ") }
|
||||
///
|
||||
/// upstream.send(1)
|
||||
/// upstream.send(2)
|
||||
/// second.send("A")
|
||||
/// upstream.send(3)
|
||||
/// upstream.send(4)
|
||||
/// // Prints "3 4"
|
||||
///
|
||||
/// - Parameter publisher: A publisher to monitor for its first emitted element.
|
||||
/// - Returns: A publisher that drops elements from the upstream publisher until
|
||||
/// the `other` publisher produces a value.
|
||||
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)
|
||||
subscriber.receive(subscription: inner)
|
||||
other.subscribe(Inner.OtherSubscriber(inner: inner))
|
||||
upstream.subscribe(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.take() {
|
||||
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.take()
|
||||
let otherSubscription = self.otherSubscription.take()
|
||||
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,81 @@
|
||||
// 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.
|
||||
///
|
||||
/// Use `drop(while:)` to omit elements from an upstream publisher until the element
|
||||
/// received meets a condition you specify.
|
||||
///
|
||||
/// In the example below, the operator omits all elements in the stream until
|
||||
/// the first element arrives that’s a positive integer, after which the operator
|
||||
/// publishes all remaining elements:
|
||||
///
|
||||
/// let numbers = [-62, -1, 0, 10, 0, 22, 41, -1, 5]
|
||||
/// cancellable = numbers.publisher
|
||||
/// .drop { $0 <= 0 }
|
||||
/// .sink { print("\($0)") }
|
||||
///
|
||||
/// // Prints: "10 0 22 41 -1 5"
|
||||
///
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and returns
|
||||
/// a Boolean value indicating whether to drop the element from the publisher’s
|
||||
/// output.
|
||||
/// - 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.
|
||||
///
|
||||
/// Use `Publisher/tryDrop(while:)` to omit elements from an upstream until
|
||||
/// an error-throwing closure you provide returns false, after which the remaining
|
||||
/// items in the stream are published. If the closure throws, no elements are emitted
|
||||
/// and the publisher fails with an error.
|
||||
///
|
||||
/// In the example below, elements are ignored until `-1` is encountered in the stream
|
||||
/// and the closure returns `false`. The publisher then republishes the remaining
|
||||
/// elements and finishes normally. Conversely, if the `guard` value in the closure
|
||||
/// had been encountered, the closure would throw and the publisher would fail with
|
||||
/// an error.
|
||||
///
|
||||
/// struct RangeError: Error {}
|
||||
/// var numbers = [1, 2, 3, 4, 5, 6, -1, 7, 8, 9, 10]
|
||||
/// let range: CountableClosedRange<Int> = (1...100)
|
||||
/// cancellable = numbers.publisher
|
||||
/// .tryDrop {
|
||||
/// guard $0 != 0 else { throw RangeError() }
|
||||
/// return range.contains($0)
|
||||
/// }
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints: "-1 7 8 9 10 completion: finished"
|
||||
/// // If instead numbers was [1, 2, 3, 4, 5, 6, 0, -1, 7, 8, 9, 10],
|
||||
/// // tryDrop(while:) would fail with a RangeError.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and returns
|
||||
/// a Boolean value indicating whether to drop the element from the publisher’s
|
||||
/// 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 +101,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 +127,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
|
||||
isCompleted = true
|
||||
// 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.
|
||||
|
||||
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>) {
|
||||
guard !isCompleted else { return }
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
predicate = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
isCompleted = true
|
||||
}
|
||||
|
||||
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())
|
||||
isCompleted = true
|
||||
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,276 @@
|
||||
${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 encoder.
|
||||
///
|
||||
/// Use `encode(encoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to encode an `Encodable` struct into `Data` that could be used to
|
||||
/// make a JSON string (or written to disk as a binary plist in the case of property
|
||||
/// lists).
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes an `Article`.
|
||||
/// The `encode(encoder:)` operator encodes the properties of the `Article` struct
|
||||
/// into a new JSON string according to the `Codable` protocol adopted by `Article`.
|
||||
/// The operator publishes the resulting JSON string to the downstream subscriber.
|
||||
/// If the encoding operation fails, which can happen in the case of complex
|
||||
/// properties that can’t be directly transformed into JSON, the stream terminates
|
||||
/// and the error is passed to the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Article, Never>()
|
||||
/// let cancellable = dataProvider
|
||||
/// .encode(encoder: JSONEncoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)") },
|
||||
/// receiveValue: { data in
|
||||
/// guard let stringRepresentation =
|
||||
/// String(data: data, encoding: .utf8) else { return }
|
||||
/// print("""
|
||||
/// Data received \(data) string representation: \
|
||||
/// \(stringRepresentation)
|
||||
/// """)
|
||||
/// })
|
||||
///
|
||||
/// dataProvider.send(Article(title: "My First Article",
|
||||
/// author: "Gita Kumar",
|
||||
/// pubDate: Date()))
|
||||
///
|
||||
/// // Prints: "Data received 86 bytes string representation:
|
||||
/// // {"title":"My First Article","author":"Gita Kumar"
|
||||
/// // "pubDate":606211803.279603}"
|
||||
///
|
||||
/// - Parameter encoder: An encoder that implements the `TopLevelEncoder` protocol.
|
||||
/// - Returns: A publisher that encodes received elements using a specified encoder,
|
||||
/// and publishes the resulting data.
|
||||
public func encode<Coder: TopLevelEncoder>(
|
||||
encoder: Coder
|
||||
) -> Publishers.Encode<Self, Coder> {
|
||||
return .init(upstream: self, encoder: encoder)
|
||||
}
|
||||
|
||||
/// Decodes the output from the upstream using a specified decoder.
|
||||
///
|
||||
/// Use `decode(type:decoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
|
||||
/// property lists) to decode data received from a `URLSession.DataTaskPublisher` or
|
||||
/// other data source using the `Decodable` protocol.
|
||||
///
|
||||
/// In this example, a `PassthroughSubject` publishes a JSON string. The JSON decoder
|
||||
/// parses the string, converting its fields according to the `Decodable` protocol
|
||||
/// implemented by `Article`, and successfully populating a new `Article`.
|
||||
/// The `Publishers.Decode` publisher then publishes the `Article` to the downstream.
|
||||
/// If a decoding operation fails, which happens in the case of missing or malformed
|
||||
/// data in the source JSON string, the stream terminates and passes the error to
|
||||
/// the downstream subscriber.
|
||||
///
|
||||
/// struct Article: Codable {
|
||||
/// let title: String
|
||||
/// let author: String
|
||||
/// let pubDate: Date
|
||||
/// }
|
||||
///
|
||||
/// let dataProvider = PassthroughSubject<Data, Never>()
|
||||
/// cancellable = dataProvider
|
||||
/// .decode(type: Article.self, decoder: JSONDecoder())
|
||||
/// .sink(receiveCompletion: { print ("Completion: \($0)")},
|
||||
/// receiveValue: { print ("value: \($0)") })
|
||||
///
|
||||
/// dataProvider.send(Data("""
|
||||
/// {\"pubDate\":1574273638.575666, \
|
||||
/// \"title\" : \"My First Article\", \
|
||||
/// \"author\" : \"Gita Kumar\" }
|
||||
/// """.utf8))
|
||||
///
|
||||
/// // Prints:
|
||||
/// // ".sink() data received Article(title: "My First Article",
|
||||
/// // author: "Gita Kumar",
|
||||
/// // pubDate: 2050-11-20 18:13:58 +0000)"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - type: The encoded data to decode into a struct that conforms to
|
||||
/// the `Decodable` protocol.
|
||||
/// - decoder: A decoder that implements the `TopLevelDecoder` protocol.
|
||||
/// - Returns: A publisher that decodes a given type using a specified decoder and
|
||||
/// publishes the result.
|
||||
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
|
||||
type: Item.Type,
|
||||
decoder: Coder
|
||||
) -> 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
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let ${instantiation.lower()}: (Upstream.Output) throws -> Output
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
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()}
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
if finished || self.subscription != nil {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
do {
|
||||
return try downstream.receive(${instantiation.lower()}(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
if finished {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
subscription = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
lock.unlock()
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user