Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c6f02c7ed | |||
| 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 |
@@ -1,297 +0,0 @@
|
||||
macOS_tests_steps: &macOS_tests_steps
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: |
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-code-coverage --build-path .build-test-debug"
|
||||
xcrun llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests \
|
||||
> coverage.txt
|
||||
- run:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: |
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-release"
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
- run:
|
||||
name: Building for testing on macOS 10.15.0 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-sdk macosx10.15 \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing on macOS 10.15.0 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-sdk macosx10.15 \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
- store_artifacts:
|
||||
path: xcodebuild_test-without-building.log
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
ubuntu_tests_steps: &ubuntu_tests_steps
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Installing dependencies
|
||||
command: |
|
||||
apt update -y
|
||||
apt upgrade -y
|
||||
apt install -y curl python3.8
|
||||
- run:
|
||||
name: "Generating LinuxMain.swift"
|
||||
command: python3.8 utils/discover_tests.py
|
||||
- run:
|
||||
name: Building and running tests in debug mode with coverage
|
||||
command: |
|
||||
make test-debug \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--enable-code-coverage \
|
||||
--disable-index-store \
|
||||
--build-path .build-test-debug"
|
||||
llvm-cov show \
|
||||
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
|
||||
.build-test-debug/debug/OpenCombinePackageTests.xctest \
|
||||
> coverage.txt
|
||||
- run:
|
||||
name: Building and running tests in debug mode with TSan
|
||||
command: |
|
||||
make test-debug-sanitize-thread \
|
||||
SWIFT_TEST_FLAGS="--disable-index-store \
|
||||
--build-path .build-test-debug-sanitize-thread" \
|
||||
- run:
|
||||
name: Building and running tests in release mode
|
||||
command: |
|
||||
make test-release \
|
||||
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
|
||||
SWIFT_TEST_FLAGS="--build-path .build-test-release"
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
"Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1.3"
|
||||
<<: *macOS_tests_steps
|
||||
|
||||
"Execute tests on macOS 10.15.0 (Xcode 12.1.0, Swift 5.3.0)":
|
||||
macos:
|
||||
xcode: "12.1.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.3.0"
|
||||
<<: *macOS_tests_steps
|
||||
|
||||
"Execute compatibility tests on iOS 14.1 (Xcode 12.1.0, Swift 5.3.0)":
|
||||
macos:
|
||||
xcode: "12.1.0"
|
||||
environment:
|
||||
SWIFT_VERSION: "5.3.0"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: make generate-compatibility-xcodeproj
|
||||
- run:
|
||||
name: Building for testing on iOS 14.1 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=14.1" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing against Combine on iOS 14.1 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 11,OS=14.1" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
- store_artifacts:
|
||||
path: xcodebuild_test-without-building.log
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
|
||||
"Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)":
|
||||
macos:
|
||||
xcode: "10.2.1"
|
||||
environment:
|
||||
BUNDLE_PATH: .bundle # path to install gems and use for caching
|
||||
SWIFT_VERSION: "5.0.1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Installing gem dependencies
|
||||
command: bundle install && bundle clean
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-simulator-cache-{{ arch }}
|
||||
- run:
|
||||
# CircleCI doesn't have an iOS 9 simulator, so we need to install it manually.
|
||||
name: Installing iOS 9 simulator
|
||||
command: |
|
||||
bundle exec xcversion simulators --install="iOS 9.3"
|
||||
bundle exec xcversion simulators
|
||||
xcrun simctl list
|
||||
- save_cache:
|
||||
key: v1-simulator-cache-{{ arch }}
|
||||
paths:
|
||||
- ~/Library/Caches/XcodeInstall
|
||||
- run:
|
||||
name: Generating Xcode project
|
||||
command: |
|
||||
make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
|
||||
xcodebuild -scheme OpenCombine-Package -showdestinations
|
||||
- run:
|
||||
name: Building for testing on iOS 9.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild build-for-testing \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_build-for-testing.log \
|
||||
| xcpretty
|
||||
- store_artifacts:
|
||||
path: xcodebuild_build-for-testing.log
|
||||
- run:
|
||||
name: Testing on iOS 9.3 with xcodebuild
|
||||
command: |
|
||||
set -o pipefail \
|
||||
&& xcodebuild test-without-building \
|
||||
-scheme OpenCombine-Package \
|
||||
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
|
||||
-derivedDataPath DerivedData \
|
||||
| tee xcodebuild_test-without-building.log \
|
||||
| xcpretty --report junit -o build/reports/results.xml
|
||||
- store_artifacts:
|
||||
path: xcodebuild_test-without-building.log
|
||||
- store_test_results:
|
||||
path: build/reports
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash) -D DerivedData
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.0)":
|
||||
docker:
|
||||
- image: swift:5.0-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.0"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.1)":
|
||||
docker:
|
||||
- image: swift:5.1-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.1"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.2)":
|
||||
docker:
|
||||
- image: swift:5.2-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.2"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Execute tests on Ubuntu 18.04 (Swift 5.3)":
|
||||
docker:
|
||||
- image: swift:5.3-bionic
|
||||
environment:
|
||||
SWIFT_VERSION: "5.3"
|
||||
<<: *ubuntu_tests_steps
|
||||
|
||||
"Run SwiftLint and Danger":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install SwiftLint
|
||||
command: |
|
||||
brew install swiftlint
|
||||
- run:
|
||||
name: Install danger-swift
|
||||
command: |
|
||||
brew install danger/tap/danger-swift
|
||||
- run:
|
||||
name: Run danger-swift
|
||||
command: danger-swift ci
|
||||
|
||||
"Run Pod spec lint":
|
||||
macos:
|
||||
xcode: "11.3.0"
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: "1"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Pod lib lint
|
||||
command: |
|
||||
pod lib lint --allow-warnings --verbose
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
"OpenCombine: execute tests on macOS":
|
||||
jobs:
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)"
|
||||
- "Execute tests on macOS 10.15.0 (Xcode 12.1.0, Swift 5.3.0)"
|
||||
"OpenCombine: execute compatibility tests":
|
||||
jobs:
|
||||
- "Execute compatibility tests on iOS 14.1 (Xcode 12.1.0, Swift 5.3.0)"
|
||||
"OpenCombine: execute tests on iOS":
|
||||
jobs:
|
||||
- "Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)"
|
||||
"OpenCombine: execute tests on Linux":
|
||||
jobs:
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.0)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.1)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.2)"
|
||||
- "Execute tests on Ubuntu 18.04 (Swift 5.3)"
|
||||
"OpenCombine: run SwiftLint and Danger":
|
||||
jobs:
|
||||
- "Run SwiftLint and Danger"
|
||||
"OpenCombine: validate podspec files":
|
||||
jobs:
|
||||
- "Run Pod spec lint"
|
||||
@@ -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
|
||||
@@ -2,7 +2,10 @@ included:
|
||||
- Sources
|
||||
- Tests
|
||||
|
||||
child_config: Tests/.swiftlint.yml
|
||||
|
||||
disabled_rules:
|
||||
- blanket_disable_command
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
- colon
|
||||
|
||||
+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,76 +0,0 @@
|
||||
import Danger
|
||||
import Foundation
|
||||
|
||||
extension StringProtocol {
|
||||
func dropSuffix<S: StringProtocol>(_ suffix: S) -> SubSequence {
|
||||
if hasSuffix(suffix) {
|
||||
return self[..<index(endIndex, offsetBy: -suffix.count)]
|
||||
} else {
|
||||
return self[...]
|
||||
}
|
||||
}
|
||||
|
||||
func directoryAndFileName() -> (SubSequence, SubSequence) {
|
||||
let lastPathSeparator = lastIndex(of: "/")
|
||||
if let lastPathSeparator = lastPathSeparator {
|
||||
return (self[..<lastPathSeparator], self[index(after: lastPathSeparator)...])
|
||||
} else {
|
||||
return (".", self[...])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let danger = Danger()
|
||||
|
||||
let allCreatedAndModified = danger.git.createdFiles + danger.git.modifiedFiles
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a GYB template but forgot to run `make gyb`.
|
||||
|
||||
let modifiedTemplates = allCreatedAndModified.filter { $0.hasSuffix(".gyb") }
|
||||
|
||||
for modifiedTemplate in modifiedTemplates {
|
||||
let (directory, filename) = modifiedTemplate.directoryAndFileName()
|
||||
let generated = "\(directory)/GENERATED-\(filename.dropSuffix(".gyb"))"
|
||||
|
||||
if !allCreatedAndModified.contains(generated) {
|
||||
fail("""
|
||||
A template \(modifiedTemplate) was modified, but the file \(generated) \
|
||||
was not regenerated.
|
||||
|
||||
Run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a generated file.
|
||||
// A template should be modified instead.
|
||||
|
||||
for modifiedGeneratedFile in danger.git.modifiedFiles
|
||||
where modifiedGeneratedFile.contains("GENERATED-")
|
||||
{
|
||||
let template = modifiedGeneratedFile
|
||||
.replacingOccurrences(of: "GENERATED-", with: "") + ".gyb"
|
||||
|
||||
if !danger.git.modifiedFiles.contains(template) {
|
||||
fail("""
|
||||
A generated file \(modifiedGeneratedFile) was modified, but \
|
||||
the template it was generated from was not modified.
|
||||
|
||||
Please modify the template \(template) instead, \
|
||||
run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwiftLint.lint(inline: true,
|
||||
configFile: ".swiftlint.yml",
|
||||
strict: true,
|
||||
lintAllFiles: true)
|
||||
|
||||
if danger.warnings.isEmpty, danger.fails.isEmpty {
|
||||
markdown("LGTM")
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.3)
|
||||
claide (1.0.3)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.4.1)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
emoji_regex (1.0.1)
|
||||
excon (0.71.0)
|
||||
faraday (0.17.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.7)
|
||||
fastlane (2.134.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 2.0)
|
||||
excon (>= 0.45.0, < 1.0.0)
|
||||
faraday (~> 0.17)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.13.1)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.21.2, < 0.24.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
jwt (~> 2.1.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_xml (~> 0.5)
|
||||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 2.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.8.1, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.23.9)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mime-types (~> 3.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.9)
|
||||
google-cloud-core (1.3.2)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.2.1)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-storage (1.16.0)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.23)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (>= 0.6.2, < 0.10.0)
|
||||
googleauth (0.6.7)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
json (2.3.1)
|
||||
jwt (2.1.0)
|
||||
memoist (0.16.1)
|
||||
mime-types (3.3)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.1009)
|
||||
mini_magick (4.9.5)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.2.6)
|
||||
naturally (2.2.0)
|
||||
os (1.0.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (2.0.5)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
rubyzip (1.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.11.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.6)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
tty-cursor (0.7.0)
|
||||
tty-screen (0.7.0)
|
||||
tty-spinner (0.9.1)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcode-install (2.6.2)
|
||||
claide (>= 0.9.1, < 1.1.0)
|
||||
fastlane (>= 2.1.0, < 3.0.0)
|
||||
xcodeproj (1.13.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.6)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
xcode-install
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.1
|
||||
+3
-3
@@ -1,17 +1,17 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombine"
|
||||
spec.version = "0.11.0"
|
||||
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/broadwaylamb/OpenCombine/"
|
||||
spec.homepage = "https://github.com/OpenCombine/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
spec.source = { :git => "https://github.com/OpenCombine/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineDispatch"
|
||||
spec.version = "0.11.0"
|
||||
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/broadwaylamb/OpenCombine/"
|
||||
spec.homepage = "https://github.com/OpenCombine/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
spec.source = { :git => "https://github.com/OpenCombine/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
@@ -21,5 +21,5 @@ Pod::Spec.new do |spec|
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineDispatch/**/*.swift"
|
||||
spec.dependency "OpenCombine", '>= 0.10.2'
|
||||
spec.dependency "OpenCombine", '>= 0.13.0'
|
||||
end
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "OpenCombineFoundation"
|
||||
spec.version = "0.11.0"
|
||||
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/broadwaylamb/OpenCombine/"
|
||||
spec.homepage = "https://github.com/OpenCombine/OpenCombine/"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
|
||||
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
spec.source = { :git => "https://github.com/OpenCombine/OpenCombine.git", :tag => "#{spec.version}" }
|
||||
|
||||
spec.swift_version = "5.0"
|
||||
|
||||
@@ -21,5 +21,5 @@ Pod::Spec.new do |spec|
|
||||
spec.tvos.deployment_target = "9.0"
|
||||
|
||||
spec.source_files = "Sources/OpenCombineFoundation/**/*.swift"
|
||||
spec.dependency "OpenCombine", '>= 0.10.2'
|
||||
spec.dependency "OpenCombine", '>= 0.13.0'
|
||||
end
|
||||
|
||||
+73
-10
@@ -1,25 +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"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
|
||||
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(
|
||||
name: "OpenCombine",
|
||||
dependencies: [
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
],
|
||||
exclude: [
|
||||
"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",
|
||||
"COpenCombineHelpers"]),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
.target(
|
||||
name: "OpenCombineFoundation",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "COpenCombineHelpers",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "OpenCombineShim",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi])))
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "OpenCombineTests",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.target(name: "OpenCombineDispatch",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
.target(name: "OpenCombineFoundation",
|
||||
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
|
||||
],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-enable-testing"]),
|
||||
.define("WASI", .when(platforms: [.wasi]))
|
||||
]
|
||||
)
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
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,37 +1,53 @@
|
||||
# OpenCombine
|
||||
[](https://circleci.com/gh/OpenCombine/OpenCombine)
|
||||
[](https://codecov.io/gh/OpenCombine/OpenCombine)
|
||||

|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/slack-OpenCombine-yellow.svg?logo=slack">](https://join.slack.com/t/opencombine/shared_invite/enQtNzE2MjE5NzkxODI0LTYxMjkzNDUxZWViZWI1Njc2YjBhODgxNjRjOTdkZTcxOGU2ZjJjZjYxMGI3NWZkN2RkNGFmZTUzNmU3MGE2ZWM)
|
||||
[](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.
|
||||
|
||||
| **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`
|
||||
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`. The same applies to Foundation: if you want to use, for instance, `NotificationCenter` or `URLSession` publishers, you'll need to also import `OpenCombineFoundation`.
|
||||
|
||||
If you develop code for multiple platforms, you may find it more convenient to import the
|
||||
`OpenCombineShim` module instead. It conditionally re-exports Combine on Apple platforms (if
|
||||
available), and all OpenCombine modules on other platforms. You can import `OpenCombineShim` only
|
||||
when using SwiftPM. It is not currently available for CocoaPods.
|
||||
|
||||
##### Swift Package Manager
|
||||
###### Swift Package
|
||||
To add `OpenCombine` to your [SPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file.
|
||||
To add `OpenCombine` to your [SwiftPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file. `OpenCombineDispatch` and `OpenCombineFoundation` products are currently not supported on WebAssembly. If your project targets WebAssembly exclusively, you should omit them from the list of your dependencies. If it targets multiple platforms including WebAssembly, depend on them only on non-WebAssembly platforms with [conditional target dependencies](https://github.com/apple/swift-evolution/blob/main/proposals/0273-swiftpm-conditional-target-dependencies.md).
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.11.0")
|
||||
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.14.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"OpenCombineFoundation"])
|
||||
.target(
|
||||
name: "MyAwesomePackage",
|
||||
dependencies: [
|
||||
"OpenCombine",
|
||||
.product(name: "OpenCombineFoundation", package: "OpenCombine"),
|
||||
.product(name: "OpenCombineDispatch", package: "OpenCombine")
|
||||
]
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
###### Xcode
|
||||
`OpenCombine` can also be added as a SPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
|
||||
`OpenCombine` can also be added as a SwiftPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
|
||||
|
||||
To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Dependency…**, enter the [repository URL](https://github.com/OpenCombine/OpenCombine.git), choose the latest available version, and activate the checkboxes:
|
||||
|
||||
@@ -44,72 +60,11 @@ To do so, open Xcode, use **File** → **Swift Packages** → **Add Package Depe
|
||||
To add `OpenCombine` to a project using [CocoaPods](https://cocoapods.org/), add `OpenCombine` and `OpenCombineDispatch` to the list of target dependencies in your `Podfile`.
|
||||
|
||||
```ruby
|
||||
pod 'OpenCombine', '~> 0.11.0'
|
||||
pod 'OpenCombineDispatch', '~> 0.11.0'
|
||||
pod 'OpenCombineFoundation', '~> 0.11.0'
|
||||
pod 'OpenCombine', '~> 0.14.0'
|
||||
pod 'OpenCombineDispatch', '~> 0.14.0'
|
||||
pod 'OpenCombineFoundation', '~> 0.14.0'
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
|
||||
|
||||
Please refer to the [issue #1](https://github.com/OpenCombine/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/OpenCombine/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
|
||||
|
||||
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`.
|
||||
|
||||
#### 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.
|
||||
@@ -122,3 +77,7 @@ Currently, `opencombine_lldb.py` defines type summaries for these types:
|
||||
|
||||
- `Subscribers.Demand`
|
||||
- That's all for now.
|
||||
|
||||
### Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
@@ -244,47 +244,6 @@ extension Publisher {
|
||||
public func collect<S>(_ strategy: Publishers.TimeGroupingStrategy<S>, options: S.SchedulerOptions? = nil) -> Publishers.CollectByTime<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct PrefixUntilOutput<Upstream, Other> : Publisher where Upstream : Publisher, Other : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Another publisher, whose first output causes this publisher to finish.
|
||||
public let other: Other
|
||||
|
||||
public init(upstream: Upstream, other: Other)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements until another publisher emits an element.
|
||||
///
|
||||
/// After the second publisher publishes an element, the publisher returned by this method finishes.
|
||||
///
|
||||
/// - Parameter publisher: A second publisher.
|
||||
/// - Returns: A publisher that republishes elements until the second publisher publishes an element.
|
||||
public func prefix<P>(untilOutputFrom publisher: P) -> Publishers.PrefixUntilOutput<Self, P> where P : Publisher
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the merge function to two upstream publishers.
|
||||
@@ -816,169 +775,6 @@ extension Publisher {
|
||||
public func throttle<S>(for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: Bool) -> Publishers.Throttle<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the zip function to two upstream publishers.
|
||||
public struct Zip<A, B> : Publisher where A : Publisher, B : Publisher, A.Failure == B.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (A.Output, B.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public init(_ a: A, _ b: B)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, B.Failure == S.Failure, S.Input == (A.Output, B.Output)
|
||||
}
|
||||
|
||||
/// A publisher created by applying the zip function to three upstream publishers.
|
||||
public struct Zip3<A, B, C> : Publisher where A : Publisher, B : Publisher, C : Publisher, A.Failure == B.Failure, B.Failure == C.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (A.Output, B.Output, C.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, C.Failure == S.Failure, S.Input == (A.Output, B.Output, C.Output)
|
||||
}
|
||||
|
||||
/// A publisher created by applying the zip function to four upstream publishers.
|
||||
public struct Zip4<A, B, C, D> : Publisher where A : Publisher, B : Publisher, C : Publisher, D : Publisher, A.Failure == B.Failure, B.Failure == C.Failure, C.Failure == D.Failure {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (A.Output, B.Output, C.Output, D.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = A.Failure
|
||||
|
||||
public let a: A
|
||||
|
||||
public let b: B
|
||||
|
||||
public let c: C
|
||||
|
||||
public let d: D
|
||||
|
||||
public init(_ a: A, _ b: B, _ c: C, _ d: D)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, D.Failure == S.Failure, S.Input == (A.Output, B.Output, C.Output, D.Output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combine elements from another publisher and deliver pairs of elements as tuples.
|
||||
///
|
||||
/// The returned publisher waits until both publishers have emitted an event, then delivers the oldest unconsumed event from each publisher together as a tuple to the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2` emits event `c`, the zip publisher emits the tuple `(a, c)`. It won’t emit a tuple with event `b` until `P2` emits another event.
|
||||
/// If either upstream publisher finishes successfuly or fails with an error, the zipped publisher does the same.
|
||||
///
|
||||
/// - Parameter other: Another publisher.
|
||||
/// - Returns: A publisher that emits pairs of elements from the upstream publishers as tuples.
|
||||
public func zip<P>(_ other: P) -> Publishers.Zip<Self, P> where P : Publisher, Self.Failure == P.Failure
|
||||
|
||||
/// Combine elements from another publisher and deliver a transformed output.
|
||||
///
|
||||
/// The returned publisher waits until both publishers have emitted an event, then delivers the oldest unconsumed event from each publisher together as a tuple to the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2` emits event `c`, the zip publisher emits the tuple `(a, c)`. It won’t emit a tuple with event `b` until `P2` emits another event.
|
||||
/// If either upstream publisher finishes successfuly or fails with an error, the zipped publisher does the same.
|
||||
///
|
||||
/// - Parameter other: Another publisher.
|
||||
/// - transform: A closure that receives the most recent value from each publisher and returns a new value to publish.
|
||||
/// - Returns: A publisher that emits pairs of elements from the upstream publishers as tuples.
|
||||
public func zip<P, T>(_ other: P, _ transform: @escaping (Self.Output, P.Output) -> T) -> Publishers.Map<Publishers.Zip<Self, P>, T> where P : Publisher, Self.Failure == P.Failure
|
||||
|
||||
/// Combine elements from two other publishers and deliver groups of elements as tuples.
|
||||
///
|
||||
/// The returned publisher waits until all three publishers have emitted an event, then delivers the oldest unconsumed event from each publisher as a tuple to the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2` emits elements `c` and `d`, and publisher `P3` emits the event `e`, the zip publisher emits the tuple `(a, c, e)`. It won’t emit a tuple with elements `b` or `d` until `P3` emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers as tuples.
|
||||
public func zip<P, Q>(_ publisher1: P, _ publisher2: Q) -> Publishers.Zip3<Self, P, Q> where P : Publisher, Q : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure
|
||||
|
||||
/// Combine elements from two other publishers and deliver a transformed output.
|
||||
///
|
||||
/// The returned publisher waits until all three publishers have emitted an event, then delivers the oldest unconsumed event from each publisher as a tuple to the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2` emits elements `c` and `d`, and publisher `P3` emits the event `e`, the zip publisher emits the tuple `(a, c, e)`. It won’t emit a tuple with elements `b` or `d` until `P3` emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - transform: A closure that receives the most recent value from each publisher and returns a new value to publish.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers as tuples.
|
||||
public func zip<P, Q, T>(_ publisher1: P, _ publisher2: Q, _ transform: @escaping (Self.Output, P.Output, Q.Output) -> T) -> Publishers.Map<Publishers.Zip3<Self, P, Q>, T> where P : Publisher, Q : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure
|
||||
|
||||
/// Combine elements from three other publishers and deliver groups of elements as tuples.
|
||||
///
|
||||
/// The returned publisher waits until all four publishers have emitted an event, then delivers the oldest unconsumed event from each publisher as a tuple to the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2` emits elements `c` and `d`, and publisher `P3` emits the elements `e` and `f`, and publisher `P4` emits the event `g`, the zip publisher emits the tuple `(a, c, e, g)`. It won’t emit a tuple with elements `b`, `d`, or `f` until `P4` emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - publisher3: A fourth publisher.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers as tuples.
|
||||
public func zip<P, Q, R>(_ publisher1: P, _ publisher2: Q, _ publisher3: R) -> Publishers.Zip4<Self, P, Q, R> where P : Publisher, Q : Publisher, R : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure, Q.Failure == R.Failure
|
||||
|
||||
/// Combine elements from three other publishers and deliver a transformed output.
|
||||
///
|
||||
/// The returned publisher waits until all four publishers have emitted an event, then delivers the oldest unconsumed event from each publisher as a tuple to the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2` emits elements `c` and `d`, and publisher `P3` emits the elements `e` and `f`, and publisher `P4` emits the event `g`, the zip publisher emits the tuple `(a, c, e, g)`. It won’t emit a tuple with elements `b`, `d`, or `f` until `P4` emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - publisher3: A fourth publisher.
|
||||
/// - transform: A closure that receives the most recent value from each publisher and returns a new value to publish.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers as tuples.
|
||||
public func zip<P, Q, R, T>(_ publisher1: P, _ publisher2: Q, _ publisher3: R, _ transform: @escaping (Self.Output, P.Output, Q.Output, R.Output) -> T) -> Publishers.Map<Publishers.Zip4<Self, P, Q, R>, T> where P : Publisher, Q : Publisher, R : Publisher, Self.Failure == P.Failure, P.Failure == Q.Failure, Q.Failure == R.Failure
|
||||
}
|
||||
|
||||
extension Publishers.CombineLatest : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
@@ -1106,19 +902,6 @@ extension Publishers.MergeMany : Equatable where Upstream : Equatable {
|
||||
public static func == (lhs: Publishers.MergeMany<Upstream>, rhs: Publishers.MergeMany<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Retry : Equatable where Upstream : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func == (lhs: Publishers.Retry<Upstream>, rhs: Publishers.Retry<Upstream>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Zip : Equatable where A : Equatable, B : Equatable {
|
||||
|
||||
/// Returns a Boolean value that indicates whether two publishers are equivalent.
|
||||
|
||||
@@ -10,13 +10,31 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <system_error>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
||||
#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 {
|
||||
@@ -47,10 +65,11 @@ public:
|
||||
virtual void unlock() = 0;
|
||||
virtual void assertOwner() {}
|
||||
|
||||
virtual ~PlatformIndependentMutex() noexcept(false) {}
|
||||
virtual ~PlatformIndependentMutex() {}
|
||||
};
|
||||
|
||||
class PThreadMutex : PlatformIndependentMutex {
|
||||
#if OPENCOMBINE_HAS_PTHREAD
|
||||
class PThreadMutex final : PlatformIndependentMutex {
|
||||
private:
|
||||
pthread_mutex_t mutex_;
|
||||
public:
|
||||
@@ -66,20 +85,16 @@ public:
|
||||
PThreadMutex(PThreadMutex&&) = delete;
|
||||
PThreadMutex& operator=(PThreadMutex&&) = delete;
|
||||
|
||||
void lock() override final {
|
||||
void lock() override {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_lock(&mutex_));
|
||||
}
|
||||
|
||||
void unlock() override final {
|
||||
void unlock() override {
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_unlock(&mutex_));
|
||||
}
|
||||
|
||||
~PThreadMutex() {
|
||||
// Yep, this destructor may throw. This is deliberate, since pthread_mutex_destroy
|
||||
// may fail.
|
||||
//
|
||||
// The altrenative is to just silently ignore the error, which is even worse.
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_destroy(&mutex_));
|
||||
pthread_mutex_destroy(&mutex_);
|
||||
}
|
||||
protected:
|
||||
class Attributes {
|
||||
@@ -107,12 +122,8 @@ protected:
|
||||
setType(PTHREAD_MUTEX_ERRORCHECK);
|
||||
}
|
||||
|
||||
~Attributes() noexcept(false) {
|
||||
// Yep, this destructor may throw. This is deliberate,
|
||||
// since pthread_mutexattr_destroy may fail.
|
||||
//
|
||||
// The altrenative is to just silently ignore the error, which is even worse.
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_destroy(&attrs_));
|
||||
~Attributes() {
|
||||
pthread_mutexattr_destroy(&attrs_);
|
||||
}
|
||||
private:
|
||||
void setType(int type) {
|
||||
@@ -124,21 +135,7 @@ protected:
|
||||
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_init(&mutex_, attributes.raw()));
|
||||
}
|
||||
};
|
||||
|
||||
class PThreadRecursiveMutex final : PThreadMutex {
|
||||
public:
|
||||
PThreadRecursiveMutex() {
|
||||
Attributes attrs;
|
||||
attrs.setRecursive();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
PThreadRecursiveMutex(const PThreadRecursiveMutex&) = delete;
|
||||
PThreadRecursiveMutex& operator=(const PThreadRecursiveMutex&) = delete;
|
||||
|
||||
PThreadRecursiveMutex(PThreadRecursiveMutex&&) = delete;
|
||||
PThreadRecursiveMutex& operator=(PThreadRecursiveMutex&&) = delete;
|
||||
};
|
||||
#endif // OPENCOMBINE_HAS_PTHREAD
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
@@ -167,9 +164,32 @@ public:
|
||||
};
|
||||
#endif // __APPLE__
|
||||
|
||||
} // end anonymous namespace
|
||||
template <typename Mu>
|
||||
class GenericMutex final : PlatformIndependentMutex {
|
||||
Mu mutex_;
|
||||
public:
|
||||
|
||||
extern "C" {
|
||||
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);
|
||||
@@ -177,24 +197,27 @@ uint64_t opencombine_next_combine_identifier(void) {
|
||||
|
||||
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (__builtin_available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) {
|
||||
return {new OSUnfairLock};
|
||||
} else {
|
||||
return {new PThreadMutex};
|
||||
}
|
||||
#else
|
||||
#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 PThreadRecursiveMutex};
|
||||
return {new StdRecursiveMutex};
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
@@ -237,7 +260,9 @@ void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lo
|
||||
}
|
||||
|
||||
void opencombine_stop_in_debugger(void) {
|
||||
#if _WIN32
|
||||
DebugBreak();
|
||||
#elif OPENCOMBINE_HAS_SIGNAL_HANDLING
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module COpenCombineHelpers {
|
||||
header "COpenCombineHelpers.h"
|
||||
}
|
||||
@@ -9,7 +9,7 @@ extension Publisher {
|
||||
|
||||
/// Wraps this publisher with a type eraser.
|
||||
///
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublishe`` to
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher`` to
|
||||
/// the downstream subscriber, rather than this publisher’s actual type.
|
||||
/// This form of _type erasure_ preserves abstraction across API boundaries, such as
|
||||
/// different modules.
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
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
|
||||
|
||||
@@ -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,24 +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 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
|
||||
}
|
||||
@@ -108,8 +108,7 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
}
|
||||
active = false
|
||||
self.completion = completion
|
||||
let downstreams = self.downstreams
|
||||
self.downstreams.removeAll()
|
||||
let downstreams = self.downstreams.take()
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.finish(completion: completion)
|
||||
@@ -181,13 +180,11 @@ extension CurrentValueSubject {
|
||||
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
guard let downstream = self.downstream.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
downstreamLock.lock()
|
||||
@@ -227,13 +224,11 @@ extension CurrentValueSubject {
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
if downstream.take() == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ public final class Future<Output, Failure: Error>: Publisher {
|
||||
return
|
||||
}
|
||||
self.result = result
|
||||
let downstreams = self.downstreams
|
||||
self.downstreams.removeAll()
|
||||
let downstreams = self.downstreams.take()
|
||||
lock.unlock()
|
||||
switch result {
|
||||
case .success(let output):
|
||||
@@ -87,12 +86,32 @@ extension Future {
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
private enum State {
|
||||
case active(Downstream, hasAnyDemand: Bool)
|
||||
case terminal
|
||||
|
||||
fileprivate var parent: Future?
|
||||
var downstream: Downstream? {
|
||||
switch self {
|
||||
case .active(let downstream, hasAnyDemand: _):
|
||||
return downstream
|
||||
case .terminal:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var downstream: Downstream?
|
||||
var hasAnyDemand: Bool {
|
||||
switch self {
|
||||
case .active(_, let hasAnyDemand):
|
||||
return hasAnyDemand
|
||||
case .terminal:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var hasAnyDemand = false
|
||||
private var parent: Future?
|
||||
|
||||
private var state: State
|
||||
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
@@ -100,7 +119,7 @@ extension Future {
|
||||
|
||||
fileprivate init(parent: Future, downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
self.state = .active(downstream, hasAnyDemand: false)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -108,21 +127,8 @@ extension Future {
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
fileprivate func fulfill(_ result: Result<Output, Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let parent = self.parent
|
||||
if case .success = result, !hasAnyDemand {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
fileprivate func lockedFulfill(downstream: Downstream,
|
||||
result: Result<Output, Failure>) {
|
||||
switch result {
|
||||
case .success(let output):
|
||||
_ = downstream.receive(output)
|
||||
@@ -130,7 +136,27 @@ extension Future {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -150,47 +176,37 @@ extension Future {
|
||||
override func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream, let parent = self.parent else {
|
||||
guard case .active(let downstream, hasAnyDemand: _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
hasAnyDemand = true
|
||||
state = .active(downstream, hasAnyDemand: true)
|
||||
|
||||
parent.lock.lock()
|
||||
guard let result = parent.result else {
|
||||
parent.lock.unlock()
|
||||
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()
|
||||
return
|
||||
}
|
||||
parent.lock.unlock()
|
||||
self.downstream = nil
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
switch result {
|
||||
case .success(let output):
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
// This branch is not reachable under normal circumstances,
|
||||
// but may be reachable in case of a race condition.
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
downstreamLock.unlock()
|
||||
parent.disassociate(self)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
switch state {
|
||||
case .active:
|
||||
state = .terminal
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
case .terminal:
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
var description: String { return "Future" }
|
||||
@@ -200,8 +216,8 @@ extension Future {
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent as Any),
|
||||
("downstream", downstream as Any),
|
||||
("hasAnyDemand", hasAnyDemand),
|
||||
("downstream", state.downstream as Any),
|
||||
("hasAnyDemand", state.hasAnyDemand),
|
||||
("subject", parent as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
|
||||
+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
|
||||
@@ -11,6 +11,12 @@ internal enum ConduitList<Output, Failure: Error> {
|
||||
case many(Set<ConduitBase<Output, Failure>>)
|
||||
}
|
||||
|
||||
extension ConduitList: HasDefaultValue {
|
||||
init() {
|
||||
self = .empty
|
||||
}
|
||||
}
|
||||
|
||||
extension ConduitList {
|
||||
internal mutating func insert(_ conduit: ConduitBase<Output, Failure>) {
|
||||
switch self {
|
||||
@@ -50,8 +56,4 @@ extension ConduitList {
|
||||
self = .many(set)
|
||||
}
|
||||
}
|
||||
|
||||
internal mutating func removeAll() {
|
||||
self = .empty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,22 @@
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
#if WASI
|
||||
internal struct __UnfairLock { // swiftlint:disable:this type_name
|
||||
internal static func allocate() -> UnfairLock { return .init() }
|
||||
internal func lock() {}
|
||||
internal func unlock() {}
|
||||
internal func assertOwner() {}
|
||||
internal func deallocate() {}
|
||||
}
|
||||
|
||||
internal struct __UnfairRecursiveLock { // swiftlint:disable:this type_name
|
||||
internal static func allocate() -> UnfairRecursiveLock { return .init() }
|
||||
internal func lock() {}
|
||||
internal func unlock() {}
|
||||
internal func deallocate() {}
|
||||
}
|
||||
#endif // WASI
|
||||
|
||||
internal typealias UnfairLock = __UnfairLock
|
||||
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
|
||||
|
||||
@@ -187,8 +187,7 @@ extension PublishedSubject {
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ internal class ReduceProducer<Downstream: Subscriber,
|
||||
|
||||
private var upstreamCompleted = false
|
||||
|
||||
private var empty = true
|
||||
|
||||
internal init(downstream: Downstream, initial: Output?, reduce: Reducer) {
|
||||
self.downstream = downstream
|
||||
self.initial = initial
|
||||
@@ -100,7 +98,9 @@ internal class ReduceProducer<Downstream: Subscriber,
|
||||
return
|
||||
}
|
||||
upstreamCompleted = true
|
||||
self.completed = downstreamRequested || empty
|
||||
if downstreamRequested {
|
||||
self.completed = true
|
||||
}
|
||||
let completed = self.completed
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
@@ -157,7 +157,6 @@ extension ReduceProducer: Subscriber {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
empty = false
|
||||
lock.unlock()
|
||||
|
||||
// Combine doesn't hold the lock when calling `receive(newValue:)`.
|
||||
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
}
|
||||
@@ -42,26 +42,58 @@ public protocol ObservableObject: AnyObject {
|
||||
var objectWillChange: ObjectWillChangePublisher { get }
|
||||
}
|
||||
|
||||
extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
// swiftlint:disable let_var_whitespace
|
||||
#if swift(>=5.1)
|
||||
/// A publisher that emits before the object has changed.
|
||||
@available(*, unavailable, message: """
|
||||
The default implementation of objectWillChange is not available yet. \
|
||||
It's being worked on in \
|
||||
https://github.com/broadwaylamb/OpenCombine/pull/97
|
||||
""")
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
fatalError("unimplemented")
|
||||
}
|
||||
#else
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
return ObservableObjectPublisher()
|
||||
}
|
||||
#endif
|
||||
// swiftlint:enable let_var_whitespace
|
||||
private protocol _ObservableObjectProperty {
|
||||
var objectWillChange: ObservableObjectPublisher? { get nonmutating set }
|
||||
}
|
||||
|
||||
#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 {
|
||||
|
||||
|
||||
@@ -85,8 +85,7 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
}
|
||||
active = false
|
||||
self.completion = completion
|
||||
let downstreams = self.downstreams
|
||||
self.downstreams.removeAll()
|
||||
let downstreams = self.downstreams.take()
|
||||
lock.unlock()
|
||||
downstreams.forEach { conduit in
|
||||
conduit.finish(completion: completion)
|
||||
@@ -168,13 +167,11 @@ extension PassthroughSubject {
|
||||
|
||||
override func finish(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard let downstream = self.downstream else {
|
||||
guard let downstream = self.downstream.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
downstreamLock.lock()
|
||||
@@ -197,13 +194,11 @@ extension PassthroughSubject {
|
||||
|
||||
override func cancel() {
|
||||
lock.lock()
|
||||
if self.downstream == nil {
|
||||
if downstream.take() == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.downstream = nil
|
||||
let parent = self.parent
|
||||
self.parent = nil
|
||||
let parent = self.parent.take()
|
||||
lock.unlock()
|
||||
parent?.disassociate(self)
|
||||
}
|
||||
|
||||
@@ -107,8 +107,16 @@ public struct Published<Value> {
|
||||
case value(Value)
|
||||
case publisher(Publisher)
|
||||
}
|
||||
@propertyWrapper
|
||||
private final class Box {
|
||||
var wrappedValue: Storage
|
||||
|
||||
private var storage: Storage
|
||||
init(wrappedValue: Storage) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
@Box private var storage: Storage
|
||||
|
||||
internal var objectWillChange: ObservableObjectPublisher? {
|
||||
get {
|
||||
@@ -119,8 +127,8 @@ public struct Published<Value> {
|
||||
return publisher.subject.objectWillChange
|
||||
}
|
||||
}
|
||||
set {
|
||||
projectedValue.subject.objectWillChange = newValue
|
||||
nonmutating set {
|
||||
getPublisher().subject.objectWillChange = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +153,7 @@ public struct Published<Value> {
|
||||
///
|
||||
/// - Parameter initialValue: The publisher's initial value.
|
||||
public init(wrappedValue: Value) {
|
||||
storage = .value(wrappedValue)
|
||||
_storage = Box(wrappedValue: .value(wrappedValue))
|
||||
}
|
||||
|
||||
/// The property for which this instance exposes a publisher.
|
||||
@@ -153,14 +161,7 @@ public struct Published<Value> {
|
||||
/// The `projectedValue` is the property accessed with the `$` operator.
|
||||
public var projectedValue: Publisher {
|
||||
mutating get {
|
||||
switch storage {
|
||||
case .value(let value):
|
||||
let publisher = Publisher(value)
|
||||
storage = .publisher(publisher)
|
||||
return publisher
|
||||
case .publisher(let publisher):
|
||||
return publisher
|
||||
}
|
||||
return getPublisher()
|
||||
}
|
||||
set { // swiftlint:disable:this unused_setter_value
|
||||
switch storage {
|
||||
@@ -172,6 +173,17 @@ public struct Published<Value> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This method can mutate `storage`
|
||||
internal func getPublisher() -> Publisher {
|
||||
switch storage {
|
||||
case .value(let value):
|
||||
let publisher = Publisher(value)
|
||||
storage = .publisher(publisher)
|
||||
return publisher
|
||||
case .publisher(let publisher):
|
||||
return publisher
|
||||
}
|
||||
}
|
||||
// swiftlint:disable let_var_whitespace
|
||||
@available(*, unavailable, message: """
|
||||
@Published is only available on properties of classes
|
||||
|
||||
@@ -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,112 +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.
|
||||
///
|
||||
/// 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -222,8 +222,7 @@ extension Publishers.Encode {
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
@@ -252,11 +251,10 @@ extension Publishers.Encode {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription else {
|
||||
guard !finished, let subscription = self.subscription.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
@@ -336,8 +334,7 @@ extension Publishers.Decode {
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
@@ -366,11 +363,10 @@ extension Publishers.Decode {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription else {
|
||||
guard !finished, let subscription = self.subscription.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable large_tuple
|
||||
|
||||
extension Publisher {
|
||||
/// Publishes the value of the key path.
|
||||
///
|
||||
@@ -31,7 +33,7 @@ extension Publisher {
|
||||
/// .sink {
|
||||
/// print ("Rolled: \($0)")
|
||||
/// }
|
||||
/// // Prints "Rolled: 6 (or some other random value).
|
||||
/// // Prints "Rolled: 4 (or some other random value).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath: The key path of a property on `Output`.
|
||||
@@ -68,7 +70,7 @@ extension Publisher {
|
||||
/// (total \(values.0 + values.1))
|
||||
/// """)
|
||||
/// }
|
||||
/// // Prints "Rolled: 5, 3 (total: 8)" (or other random values).
|
||||
/// // Prints "Rolled: 4, 1 (total: 5)" (or other random values).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`.
|
||||
@@ -110,7 +112,7 @@ extension Publisher {
|
||||
/// (total \(values.0 + values.1 + values.2))
|
||||
/// """)
|
||||
/// }
|
||||
/// // Prints "Rolled: 2, 4, 3 (total: 9)" (or other random values).
|
||||
/// // Prints "Rolled: 3, 5, 4 (total: 12)" (or other random values).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`.
|
||||
|
||||
@@ -293,8 +293,7 @@ extension Just {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
guard let downstream = self.downstream.take() else { return }
|
||||
_ = downstream.receive(value)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
@@ -122,8 +122,7 @@ extension Optional.OCombine {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
guard let downstream = self.downstream.take() else { return }
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ extension Publisher {
|
||||
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes
|
||||
/// all received input.
|
||||
///
|
||||
/// Use `assertNoFailure()` for internal sanity checks that are active during testing.
|
||||
/// However, it is important to note that, like its Swift counterpart
|
||||
/// 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 in both development/testing _and_ shipping versions of code.
|
||||
/// 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
|
||||
@@ -57,8 +57,8 @@ extension Publishers {
|
||||
/// A publisher that raises a fatal error upon receiving any failure, and otherwise
|
||||
/// republishes all received input.
|
||||
///
|
||||
/// Use this function for internal sanity checks that are active during testing but
|
||||
/// do not impact performance of shipping code.
|
||||
/// 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
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 03.12.2019.
|
||||
//
|
||||
|
||||
#if !WASI
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
@@ -225,3 +227,5 @@ extension Publishers.Breakpoint {
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !WASI
|
||||
|
||||
@@ -331,9 +331,7 @@ extension Publishers.Buffer {
|
||||
private func lockedPop(_ demand: Subscribers.Demand) -> [Input] {
|
||||
assert(demand > 0)
|
||||
guard let max = demand.max else {
|
||||
let poppedValues = self.values
|
||||
self.values = []
|
||||
return poppedValues
|
||||
return values.take()
|
||||
}
|
||||
|
||||
let poppedValues = Array(values.prefix(max))
|
||||
|
||||
@@ -128,8 +128,7 @@ extension Publishers.CollectByCount {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let output = self.buffer
|
||||
self.buffer = []
|
||||
let output = self.buffer.take()
|
||||
lock.unlock()
|
||||
return downstream.receive(output) * count
|
||||
}
|
||||
@@ -143,8 +142,7 @@ extension Publishers.CollectByCount {
|
||||
if buffer.isEmpty {
|
||||
lock.unlock()
|
||||
} else {
|
||||
let buffer = self.buffer
|
||||
self.buffer = []
|
||||
let buffer = self.buffer.take()
|
||||
lock.unlock()
|
||||
_ = downstream.receive(buffer)
|
||||
}
|
||||
@@ -168,10 +166,9 @@ extension Publishers.CollectByCount {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
if let subscription = self.subscription {
|
||||
if let subscription = self.subscription.take() {
|
||||
buffer = []
|
||||
finished = true
|
||||
self.subscription = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
} else {
|
||||
|
||||
@@ -233,7 +233,7 @@ extension Publishers.Concatenate {
|
||||
|
||||
private var suffixState = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let suffix: Suffix
|
||||
private var suffix: Suffix?
|
||||
|
||||
private var pending = Subscribers.Demand.none
|
||||
|
||||
@@ -266,8 +266,18 @@ extension Publishers.Concatenate {
|
||||
prefixState.subscription ?? suffixState.subscription
|
||||
prefixState = .terminal
|
||||
suffixState = .terminal
|
||||
lock.unlock()
|
||||
upstreamSubscription?.cancel()
|
||||
|
||||
// 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" }
|
||||
@@ -320,7 +330,7 @@ extension Publishers.Concatenate {
|
||||
lock.unlock()
|
||||
switch completion {
|
||||
case .finished:
|
||||
suffix.subscribe(SuffixSubscriber(inner: self))
|
||||
suffix?.subscribe(SuffixSubscriber(inner: self))
|
||||
case .failure:
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
@@ -212,8 +212,7 @@ extension Publishers.Debounce {
|
||||
let generation = currentGeneration
|
||||
currentValue = input
|
||||
let due = scheduler.now.advanced(by: dueTime)
|
||||
let previousCancellers = self.currentCancellers
|
||||
currentCancellers.removeAll()
|
||||
let previousCancellers = self.currentCancellers.take()
|
||||
currentCancellers[generation] = .pending
|
||||
lock.unlock()
|
||||
let newCanceller = scheduler.schedule(after: due,
|
||||
@@ -238,8 +237,7 @@ extension Publishers.Debounce {
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let previousCancellers = currentCancellers
|
||||
currentCancellers.removeAll()
|
||||
let previousCancellers = currentCancellers.take()
|
||||
lock.unlock()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
@@ -268,8 +266,7 @@ extension Publishers.Debounce {
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
let previousCancellers = currentCancellers
|
||||
currentCancellers.removeAll()
|
||||
let previousCancellers = currentCancellers.take()
|
||||
lock.unlock()
|
||||
for canceller in previousCancellers.values {
|
||||
canceller.cancel()
|
||||
@@ -306,11 +303,10 @@ extension Publishers.Debounce {
|
||||
return
|
||||
}
|
||||
|
||||
guard let canceller = currentCancellers[generation] else {
|
||||
guard let canceller = currentCancellers[generation].take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
currentCancellers[generation] = nil
|
||||
|
||||
let hasAnyDemand = downstreamDemand != .none
|
||||
if hasAnyDemand {
|
||||
|
||||
@@ -139,8 +139,7 @@ extension Publishers.Drop {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
}
|
||||
|
||||
@@ -204,8 +204,7 @@ extension Publishers.DropUntilOutput {
|
||||
}
|
||||
|
||||
otherFinished = true
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
self.upstreamSubscription = nil
|
||||
if let upstreamSubscription = self.upstreamSubscription.take() {
|
||||
lock.unlock()
|
||||
upstreamSubscription.cancel()
|
||||
} else {
|
||||
@@ -229,10 +228,8 @@ extension Publishers.DropUntilOutput {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let upstreamSubscription = self.upstreamSubscription
|
||||
let otherSubscription = self.otherSubscription
|
||||
self.upstreamSubscription = nil
|
||||
self.otherSubscription = nil
|
||||
let upstreamSubscription = self.upstreamSubscription.take()
|
||||
let otherSubscription = self.otherSubscription.take()
|
||||
cancelled = true
|
||||
lock.unlock()
|
||||
|
||||
|
||||
@@ -221,8 +221,7 @@ extension Publishers.${instantiation} {
|
||||
} catch {
|
||||
lock.lock()
|
||||
finished = true
|
||||
let subscription = self.subscription
|
||||
self.subscription = nil
|
||||
let subscription = self.subscription.take()
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
@@ -251,11 +250,10 @@ extension Publishers.${instantiation} {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard !finished, let subscription = self.subscription else {
|
||||
guard !finished, let subscription = self.subscription.take() else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.subscription = nil
|
||||
finished = true
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
|
||||
@@ -14,7 +14,7 @@ extension Publisher {
|
||||
/// the elements from one kind of publisher into a new publisher that is sent
|
||||
/// to subscribers. Use `flatMap(maxPublishers:_:)` when you want to create a new
|
||||
/// series of events for downstream subscribers based on the received value.
|
||||
/// The closure creates the new `Publishe`` based on the received value.
|
||||
/// The closure creates the new `Publisher` based on the received value.
|
||||
/// The new `Publisher` can emit more than one event, and successful completion of
|
||||
/// the new `Publisher` does not complete the overall stream.
|
||||
/// Failure of the new `Publisher` will fail the overall stream.
|
||||
@@ -157,17 +157,17 @@ extension Publishers {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
let outer = Outer(downstream: subscriber,
|
||||
maxPublishers: maxPublishers,
|
||||
map: transform)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: outer)
|
||||
upstream.subscribe(outer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
private final class Outer<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
@@ -243,7 +243,7 @@ extension Publishers.FlatMap {
|
||||
subscription.request(maxPublishers)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
let cancelledOrCompleted = self.cancelledOrCompleted
|
||||
lock.unlock()
|
||||
@@ -260,9 +260,9 @@ extension Publishers.FlatMap {
|
||||
return .none
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
|
||||
outerSubscription = nil
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
outerSubscription = nil
|
||||
outerFinished = true
|
||||
switch completion {
|
||||
case .finished:
|
||||
@@ -272,6 +272,8 @@ extension Publishers.FlatMap {
|
||||
let wasAlreadyCancelledOrCompleted = cancelledOrCompleted
|
||||
cancelledOrCompleted = true
|
||||
for (_, subscription) in subscriptions {
|
||||
// Cancelling subscriptions with the lock acquired. Not good,
|
||||
// but that's what Combine does. This code path is tested.
|
||||
subscription.cancel()
|
||||
}
|
||||
subscriptions = [:]
|
||||
@@ -302,8 +304,7 @@ extension Publishers.FlatMap {
|
||||
}
|
||||
if demand == .unlimited {
|
||||
downstreamDemand = .unlimited
|
||||
let buffer = self.buffer
|
||||
self.buffer = []
|
||||
let buffer = self.buffer.take()
|
||||
let subscriptions = self.subscriptions
|
||||
lock.unlock()
|
||||
downstreamLock.lock()
|
||||
@@ -354,16 +355,19 @@ extension Publishers.FlatMap {
|
||||
|
||||
fileprivate func cancel() {
|
||||
lock.lock()
|
||||
if cancelledOrCompleted {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelledOrCompleted = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = [:]
|
||||
let subscriptions = self.subscriptions.take()
|
||||
let outerSubscription = self.outerSubscription.take()
|
||||
lock.unlock()
|
||||
for (_, subscription) in subscriptions {
|
||||
subscription.cancel()
|
||||
}
|
||||
// Combine doesn't acquire the lock here. Weird.
|
||||
// Combine doesn't acquire outerLock here. Weird.
|
||||
outerSubscription?.cancel()
|
||||
outerSubscription = nil
|
||||
}
|
||||
|
||||
// MARK: - Reflection
|
||||
@@ -443,8 +447,7 @@ extension Publishers.FlatMap {
|
||||
return
|
||||
}
|
||||
cancelledOrCompleted = true
|
||||
let subscriptions = self.subscriptions
|
||||
self.subscriptions = [:]
|
||||
let subscriptions = self.subscriptions.take()
|
||||
lock.unlock()
|
||||
for (i, subscription) in subscriptions where i != index {
|
||||
subscription.cancel()
|
||||
@@ -471,9 +474,9 @@ extension Publishers.FlatMap {
|
||||
private func releaseLockThenSendCompletionDownstreamIfNeeded(
|
||||
outerFinished: Bool
|
||||
) -> Bool {
|
||||
#if DEBUG
|
||||
#if DEBUG
|
||||
lock.assertOwner() // Sanity check
|
||||
#endif
|
||||
#endif
|
||||
if !cancelledOrCompleted && outerFinished && buffer.isEmpty &&
|
||||
subscriptions.count + pendingSubscriptions == 0 {
|
||||
cancelledOrCompleted = true
|
||||
@@ -495,10 +498,10 @@ extension Publishers.FlatMap {
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible {
|
||||
private let index: SubscriptionIndex
|
||||
private let inner: Inner
|
||||
private let inner: Outer
|
||||
fileprivate let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(index: SubscriptionIndex, inner: Inner) {
|
||||
fileprivate init(index: SubscriptionIndex, inner: Outer) {
|
||||
self.index = index
|
||||
self.inner = inner
|
||||
}
|
||||
|
||||
@@ -139,12 +139,12 @@ extension Publishers.HandleEvents {
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
private let lock = UnfairLock.allocate()
|
||||
public var receiveSubscription: ((Subscription) -> Void)?
|
||||
public var receiveOutput: ((Upstream.Output) -> Void)?
|
||||
public var receiveCompletion:
|
||||
fileprivate var receiveSubscription: ((Subscription) -> Void)?
|
||||
fileprivate var receiveOutput: ((Upstream.Output) -> Void)?
|
||||
fileprivate var receiveCompletion:
|
||||
((Subscribers.Completion<Upstream.Failure>) -> Void)?
|
||||
public var receiveCancel: (() -> Void)?
|
||||
public var receiveRequest: ((Subscribers.Demand) -> Void)?
|
||||
fileprivate var receiveCancel: (() -> Void)?
|
||||
fileprivate var receiveRequest: ((Subscribers.Demand) -> Void)?
|
||||
private let downstream: Downstream
|
||||
|
||||
init(_ events: Publishers.HandleEvents<Upstream>, downstream: Downstream) {
|
||||
|
||||
@@ -6,6 +6,8 @@ ${template_header}
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable large_tuple
|
||||
|
||||
%{
|
||||
from gyb_opencombine_support import (
|
||||
suffix_variadic,
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// Publishers.PrefixUntilOutput.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.11.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes elements until another publisher emits an element.
|
||||
///
|
||||
/// After the second publisher publishes an element, the publisher returned by this
|
||||
/// method finishes.
|
||||
///
|
||||
/// - Parameter publisher: A second publisher.
|
||||
/// - Returns: A publisher that republishes elements until the second publisher
|
||||
/// publishes an element.
|
||||
public func prefix<Other: Publisher>(
|
||||
untilOutputFrom publisher: Other
|
||||
) -> Publishers.PrefixUntilOutput<Self, Other> {
|
||||
return .init(upstream: self, other: publisher)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
public struct PrefixUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Another publisher, whose first output causes this publisher to finish.
|
||||
public let other: Other
|
||||
|
||||
public init(upstream: Upstream, other: Other) {
|
||||
self.upstream = upstream
|
||||
self.other = other
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, trigger: other))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.PrefixUntilOutput {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private struct Termination: Subscriber {
|
||||
|
||||
let inner: Inner
|
||||
|
||||
var combineIdentifier: CombineIdentifier {
|
||||
return inner.combineIdentifier
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
inner.terminationReceive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Other.Output) -> Subscribers.Demand {
|
||||
return inner.terminationReceive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Other.Failure>) {
|
||||
inner.terminationReceive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private var termination: Termination?
|
||||
private var prefixState = SubscriptionStatus.awaitingSubscription
|
||||
private var terminationState = SubscriptionStatus.awaitingSubscription
|
||||
private var triggered = false
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let downstream: Downstream
|
||||
|
||||
init(downstream: Downstream, trigger: Other) {
|
||||
self.downstream = downstream
|
||||
let termination = Termination(inner: self)
|
||||
self.termination = termination
|
||||
trigger.subscribe(termination)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = prefixState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
prefixState = triggered ? .terminal : .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = prefixState else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
let prefixState = self.prefixState
|
||||
let terminationSubscription = terminationState.subscription
|
||||
self.prefixState = .terminal
|
||||
terminationState = .terminal
|
||||
termination = nil
|
||||
lock.unlock()
|
||||
terminationSubscription?.cancel()
|
||||
if case .subscribed = prefixState {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = prefixState else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
let prefixSubscription = prefixState.subscription
|
||||
let terminationSubscription = terminationState.subscription
|
||||
prefixState = .terminal
|
||||
terminationState = .terminal
|
||||
lock.unlock()
|
||||
prefixSubscription?.cancel()
|
||||
terminationSubscription?.cancel()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func terminationReceive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = terminationState else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
terminationState = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
subscription.request(.max(1))
|
||||
}
|
||||
|
||||
private func terminationReceive(_ input: Other.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = terminationState else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
let prefixSubscription = prefixState.subscription
|
||||
prefixState = .terminal
|
||||
terminationState = .terminal
|
||||
termination = nil
|
||||
triggered = true
|
||||
lock.unlock()
|
||||
prefixSubscription?.cancel()
|
||||
downstream.receive(completion: .finished)
|
||||
return .none
|
||||
}
|
||||
|
||||
private func terminationReceive(
|
||||
completion: Subscribers.Completion<Other.Failure>
|
||||
) {
|
||||
lock.lock()
|
||||
terminationState = .terminal
|
||||
termination = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,7 @@ extension Publishers {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, output: output)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
upstream.subscribe(Inner(downstream: subscriber, output: output))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,11 +121,8 @@ extension Publishers.ReplaceError {
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
let pendingDemand = self.pendingDemand
|
||||
lock.unlock()
|
||||
if pendingDemand != .none {
|
||||
subscription.request(pendingDemand)
|
||||
}
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
@@ -150,7 +145,7 @@ extension Publishers.ReplaceError {
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
guard case .subscribed = status, !terminated else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
//
|
||||
// Publishers.Retry.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.06.2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Attempts to recreate a failed subscription with the upstream publisher up to
|
||||
/// the number of times you specify.
|
||||
///
|
||||
/// Use `retry(_:)` to try connecting to an upstream publisher after a failed
|
||||
/// connection attempt.
|
||||
///
|
||||
/// In the example below, a `URLSession.DataTaskPublisher` attempts to connect to
|
||||
/// a remote URL. If the connection attempt succeeds, it publishes the remote
|
||||
/// service’s HTML to the downstream publisher and completes normally. Otherwise,
|
||||
/// the retry operator attempts to reestablish the connection. If after three attempts
|
||||
/// the publisher still can’t connect to the remote URL, the `catch(_:)` operator
|
||||
/// replaces the error with a new publisher that publishes a “connection timed out”
|
||||
/// HTML page. After the downstream subscriber receives the timed out message,
|
||||
/// the stream completes normally.
|
||||
///
|
||||
/// struct WebSiteData: Codable {
|
||||
/// var rawHTML: String
|
||||
/// }
|
||||
///
|
||||
/// let myURL = URL(string: "https://www.example.com")
|
||||
///
|
||||
/// cancellable = URLSession.shared.dataTaskPublisher(for: myURL!)
|
||||
/// .retry(3)
|
||||
/// .map { page -> WebSiteData in
|
||||
/// WebSiteData(rawHTML: String(decoding: page.data, as: UTF8.self))
|
||||
/// }
|
||||
/// .catch { error in
|
||||
/// Just(
|
||||
/// WebSiteData(
|
||||
/// rawHTML: "<HTML>Unable to load page - timed out.</HTML>"
|
||||
/// )
|
||||
/// )
|
||||
/// }
|
||||
/// .sink(receiveCompletion: { print ("completion: \($0)") },
|
||||
/// receiveValue: { print ("value: \($0)") })
|
||||
///
|
||||
/// // Prints: The HTML content from the remote URL upon a successful connection,
|
||||
/// // or returns "<HTML>Unable to load page - timed out.</HTML>" if
|
||||
/// // the number of retries exceeds the specified value.
|
||||
///
|
||||
/// After exceeding the specified number of retries, the publisher passes the failure
|
||||
/// to the downstream receiver.
|
||||
/// - Parameter retries: The number of times to attempt to recreate the subscription.
|
||||
/// - Returns: A publisher that attempts to recreate its subscription to a failed
|
||||
/// upstream publisher.
|
||||
public func retry(_ retries: Int) -> Publishers.Retry<Self> {
|
||||
return .init(upstream: self, retries: retries)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that attempts to recreate its subscription to a failed upstream
|
||||
/// publisher.
|
||||
public struct Retry<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The maximum number of retry attempts to perform.
|
||||
///
|
||||
/// If `nil`, this publisher attempts to reconnect with the upstream publisher
|
||||
/// an unlimited number of times.
|
||||
public let retries: Int?
|
||||
|
||||
/// Creates a publisher that attempts to recreate its subscription to a failed
|
||||
/// upstream publisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - upstream: The publisher from which this publisher receives its elements.
|
||||
/// - retries: The maximum number of retry attempts to perform. If `nil`, this
|
||||
/// publisher attempts to reconnect with the upstream publisher an unlimited
|
||||
/// number of times.
|
||||
public init(upstream: Upstream, retries: Int?) {
|
||||
self.upstream = upstream
|
||||
self.retries = retries
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(parent: self, downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Retry: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Retry {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case ready(Publishers.Retry<Upstream>, Downstream)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private enum Chances {
|
||||
case finite(Int)
|
||||
case infinite
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state: State
|
||||
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
private var remaining: Chances
|
||||
|
||||
private var downstreamNeedsSubscription = true
|
||||
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
|
||||
private var completionRecursion = false
|
||||
|
||||
private var needsSubscribe = false
|
||||
|
||||
init(parent: Publishers.Retry<Upstream>, downstream: Downstream) {
|
||||
state = .ready(parent, downstream)
|
||||
remaining = parent.retries.map(Chances.finite) ?? .infinite
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(_, downstream) = state, upstreamSubscription == nil
|
||||
else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = subscription
|
||||
let downstreamDemand = self.downstreamDemand
|
||||
let downstreamNeedsSubscription = self.downstreamNeedsSubscription
|
||||
self.downstreamNeedsSubscription = false
|
||||
lock.unlock()
|
||||
if downstreamNeedsSubscription {
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
if downstreamDemand != .none {
|
||||
subscription.request(downstreamDemand)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .ready(_, downstream) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
downstreamDemand -= 1
|
||||
lock.unlock()
|
||||
|
||||
let newDemand = downstream.receive(input)
|
||||
|
||||
if newDemand == .none { return .none }
|
||||
|
||||
lock.lock()
|
||||
downstreamDemand += newDemand
|
||||
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
lock.unlock()
|
||||
upstreamSubscription.request(newDemand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .ready(parent, downstream) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if case .failure = completion {
|
||||
upstreamSubscription = nil
|
||||
switch remaining {
|
||||
case .finite(0):
|
||||
break
|
||||
case .finite(let attempts):
|
||||
remaining = .finite(attempts - 1)
|
||||
fallthrough
|
||||
case .infinite:
|
||||
if completionRecursion {
|
||||
needsSubscribe = true
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
repeat {
|
||||
completionRecursion = true
|
||||
needsSubscribe = false
|
||||
lock.unlock()
|
||||
parent.upstream.subscribe(self)
|
||||
lock.lock()
|
||||
completionRecursion = false
|
||||
} while needsSubscribe
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case .ready = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamDemand += demand
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
lock.unlock()
|
||||
upstreamSubscription.request(demand)
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case .ready = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
if let upstreamSubscription = self.upstreamSubscription {
|
||||
lock.unlock()
|
||||
upstreamSubscription.cancel()
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var description: String { return "Retry" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -230,8 +230,7 @@ extension Publishers.SwitchToLatest {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let currentInnerSubscription = self.currentInnerSubscription {
|
||||
self.currentInnerSubscription = nil
|
||||
if let currentInnerSubscription = self.currentInnerSubscription.take() {
|
||||
lock.unlock()
|
||||
currentInnerSubscription.cancel()
|
||||
lock.lock()
|
||||
@@ -272,8 +271,7 @@ extension Publishers.SwitchToLatest {
|
||||
lock.unlock()
|
||||
}
|
||||
case .failure:
|
||||
let currentInnerSubscription = self.currentInnerSubscription
|
||||
self.currentInnerSubscription = nil
|
||||
let currentInnerSubscription = self.currentInnerSubscription.take()
|
||||
sentCompletion = true
|
||||
lock.unlock()
|
||||
currentInnerSubscription?.cancel()
|
||||
@@ -298,10 +296,8 @@ extension Publishers.SwitchToLatest {
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
cancelled = true
|
||||
let currentInnerSubscription = self.currentInnerSubscription
|
||||
self.currentInnerSubscription = nil
|
||||
let outerSubscription = self.outerSubscription
|
||||
self.outerSubscription = nil
|
||||
let currentInnerSubscription = self.currentInnerSubscription.take()
|
||||
let outerSubscription = self.outerSubscription.take()
|
||||
lock.unlock()
|
||||
|
||||
currentInnerSubscription?.cancel()
|
||||
@@ -386,8 +382,7 @@ extension Publishers.SwitchToLatest {
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
let outerSubscription = self.outerSubscription
|
||||
self.outerSubscription = nil
|
||||
let outerSubscription = self.outerSubscription.take()
|
||||
sentCompletion = true
|
||||
lock.unlock()
|
||||
outerSubscription?.cancel()
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// Publishers.Throttle.swift
|
||||
//
|
||||
//
|
||||
// Created by Stuart Austin on 14/11/2020.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
// swiftlint:disable generic_type_name line_length
|
||||
|
||||
/// Publishes either the most-recent or first element published by the upstream
|
||||
/// publisher in the specified time interval.
|
||||
///
|
||||
/// Use `throttle(for:scheduler:latest:`` to selectively republish elements from
|
||||
/// an upstream publisher during an interval you specify. Other elements received from
|
||||
/// the upstream in the throttling interval aren’t republished.
|
||||
///
|
||||
/// In the example below, a `Timer.TimerPublisher` produces elements on 3-second
|
||||
/// intervals; the `throttle(for:scheduler:latest:)` operator delivers the first
|
||||
/// event, then republishes only the latest event in the following ten second
|
||||
/// intervals:
|
||||
///
|
||||
/// cancellable = Timer.publish(every: 3.0, on: .main, in: .default)
|
||||
/// .autoconnect()
|
||||
/// .print("\(Date().description)")
|
||||
/// .throttle(for: 10.0, scheduler: RunLoop.main, latest: true)
|
||||
/// .sink(
|
||||
/// receiveCompletion: { print ("Completion: \($0).") },
|
||||
/// receiveValue: { print("Received Timestamp \($0).") }
|
||||
/// )
|
||||
///
|
||||
/// // Prints:
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:26:57 +0000)
|
||||
/// // Received Timestamp 2020-03-19 18:26:57 +0000.
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:00 +0000)
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:03 +0000)
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:06 +0000)
|
||||
/// // Publish at: 2020-03-19 18:26:54 +0000: receive value: (2020-03-19 18:27:09 +0000)
|
||||
/// // Received Timestamp 2020-03-19 18:27:09 +0000.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: The interval at which to find and emit either the most recent or
|
||||
/// the first element, expressed in the time system of the scheduler.
|
||||
/// - scheduler: The scheduler on which to publish elements.
|
||||
/// - latest: A Boolean value that indicates whether to publish the most recent
|
||||
/// element. If `false`, the publisher emits the first element received during
|
||||
/// the interval.
|
||||
/// - Returns: A publisher that emits either the most-recent or first element received
|
||||
/// during the specified interval.
|
||||
public func throttle<S>(for interval: S.SchedulerTimeType.Stride,
|
||||
scheduler: S,
|
||||
latest: Bool) -> Publishers.Throttle<Self, S>
|
||||
where S: Scheduler
|
||||
{
|
||||
return .init(upstream: self,
|
||||
interval: interval,
|
||||
scheduler: scheduler,
|
||||
latest: latest)
|
||||
}
|
||||
|
||||
// swiftlint:enable generic_type_name line_length
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes either the most-recent or first element published by
|
||||
/// the upstream publisher in a specified time interval.
|
||||
public struct Throttle<Upstream, Context>: Publisher
|
||||
where Upstream: Publisher, Context: Scheduler
|
||||
{
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The interval in which to find and emit the most recent element.
|
||||
public let interval: Context.SchedulerTimeType.Stride
|
||||
|
||||
/// The scheduler on which to publish elements.
|
||||
public let scheduler: Context
|
||||
|
||||
/// A Boolean value indicating whether to publish the most recent element.
|
||||
///
|
||||
/// If `false`, the publisher emits the first element received during
|
||||
/// the interval.
|
||||
public let latest: Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
latest: Bool) {
|
||||
|
||||
self.upstream = upstream
|
||||
self.interval = interval
|
||||
self.scheduler = scheduler
|
||||
self.latest = latest
|
||||
}
|
||||
|
||||
// swiftlint:disable generic_type_name
|
||||
|
||||
/// Attaches the specified subscriber to this publisher.
|
||||
///
|
||||
/// Implementations of ``Publisher`` must implement this method.
|
||||
///
|
||||
/// The provided implementation of ``Publisher/subscribe(_:)-4u8kn``calls
|
||||
/// this method.
|
||||
///
|
||||
/// - Parameter subscriber: The subscriber to attach to this ``Publisher``,
|
||||
/// after which it can receive values.
|
||||
public func receive<S>(subscriber: S)
|
||||
where S: Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input
|
||||
{
|
||||
let inner = Inner(interval: interval,
|
||||
scheduler: scheduler,
|
||||
latest: latest,
|
||||
downstream: subscriber)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
|
||||
// swiftlint:enable generic_type_name
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Throttle {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case awaitingSubscription(Downstream)
|
||||
case subscribed(Subscription, Downstream)
|
||||
case pendingTerminal(Subscription, Downstream)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let interval: Context.SchedulerTimeType.Stride
|
||||
private let scheduler: Context
|
||||
private let latest: Bool
|
||||
private var state: State
|
||||
private let downstreamLock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var lastEmissionTime: Context.SchedulerTimeType?
|
||||
|
||||
private var pendingInput: Input?
|
||||
private var pendingCompletion: Subscribers.Completion<Failure>?
|
||||
|
||||
private var demand: Subscribers.Demand = .none
|
||||
|
||||
private var lastTime: Context.SchedulerTimeType
|
||||
|
||||
init(interval: Context.SchedulerTimeType.Stride,
|
||||
scheduler: Context,
|
||||
latest: Bool,
|
||||
downstream: Downstream) {
|
||||
self.state = .awaitingSubscription(downstream)
|
||||
self.interval = interval
|
||||
self.scheduler = scheduler
|
||||
self.latest = latest
|
||||
|
||||
self.lastTime = scheduler.now
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
downstreamLock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .awaitingSubscription(downstream) = state else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.lastTime = scheduler.now
|
||||
|
||||
state = .subscribed(subscription, downstream)
|
||||
lock.unlock()
|
||||
|
||||
subscription.request(.unlimited)
|
||||
|
||||
downstreamLock.lock()
|
||||
downstream.receive(subscription: self)
|
||||
downstreamLock.unlock()
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
let lastTime = scheduler.now
|
||||
self.lastTime = lastTime
|
||||
|
||||
guard demand > .none else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
|
||||
let hasScheduledOutput = (pendingInput != nil || pendingCompletion != nil)
|
||||
|
||||
if hasScheduledOutput && latest {
|
||||
pendingInput = input
|
||||
lock.unlock()
|
||||
} else if !hasScheduledOutput {
|
||||
let minimumEmissionTime =
|
||||
lastEmissionTime.map { $0.advanced(by: interval) }
|
||||
|
||||
let emissionTime =
|
||||
minimumEmissionTime.map { Swift.max(lastTime, $0) } ?? lastTime
|
||||
|
||||
demand -= 1
|
||||
|
||||
pendingInput = input
|
||||
lock.unlock()
|
||||
|
||||
let action: () -> Void = { [weak self] in
|
||||
self?.scheduledEmission()
|
||||
}
|
||||
|
||||
if emissionTime == lastTime {
|
||||
scheduler.schedule(action)
|
||||
} else {
|
||||
scheduler.schedule(after: emissionTime, action)
|
||||
}
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription, downstream) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let lastTime = scheduler.now
|
||||
self.lastTime = lastTime
|
||||
state = .pendingTerminal(subscription, downstream)
|
||||
|
||||
let hasScheduledOutput = (pendingInput != nil || pendingCompletion != nil)
|
||||
|
||||
if hasScheduledOutput && pendingCompletion == nil {
|
||||
pendingCompletion = completion
|
||||
lock.unlock()
|
||||
} else if !hasScheduledOutput {
|
||||
pendingCompletion = completion
|
||||
lock.unlock()
|
||||
|
||||
scheduler.schedule { [weak self] in
|
||||
self?.scheduledEmission()
|
||||
}
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduledEmission() {
|
||||
lock.lock()
|
||||
|
||||
let downstream: Downstream
|
||||
|
||||
switch state {
|
||||
case .awaitingSubscription, .terminal:
|
||||
lock.unlock()
|
||||
return
|
||||
case let .subscribed(_, foundDownstream),
|
||||
let .pendingTerminal(_, foundDownstream):
|
||||
downstream = foundDownstream
|
||||
}
|
||||
|
||||
if self.pendingInput != nil && self.pendingCompletion == nil {
|
||||
lastEmissionTime = scheduler.now
|
||||
}
|
||||
|
||||
let pendingInput = self.pendingInput.take()
|
||||
let pendingCompletion = self.pendingCompletion.take()
|
||||
|
||||
if pendingCompletion != nil {
|
||||
state = .terminal
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
|
||||
downstreamLock.lock()
|
||||
|
||||
let newDemand: Subscribers.Demand
|
||||
if let input = pendingInput {
|
||||
newDemand = downstream.receive(input)
|
||||
} else {
|
||||
newDemand = .none
|
||||
}
|
||||
|
||||
if let completion = pendingCompletion {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
downstreamLock.unlock()
|
||||
|
||||
guard newDemand > 0 else { return }
|
||||
self.lock.lock()
|
||||
demand += newDemand
|
||||
self.lock.unlock()
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard demand > 0 else { return }
|
||||
lock.lock()
|
||||
guard case .subscribed = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.demand += demand
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
|
||||
let subscription: Subscription?
|
||||
switch state {
|
||||
case let .subscribed(existingSubscription, _),
|
||||
let .pendingTerminal(existingSubscription, _):
|
||||
subscription = existingSubscription
|
||||
case .awaitingSubscription, .terminal:
|
||||
subscription = nil
|
||||
}
|
||||
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
|
||||
subscription?.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "Throttle" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
//
|
||||
// Publishers.Zip.swift
|
||||
//
|
||||
// Created by Eric Patey on 29.08.2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable large_tuple
|
||||
|
||||
#if canImport(COpenCombineHelpers)
|
||||
import COpenCombineHelpers
|
||||
#endif
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher created by applying the zip function to two upstream publishers.
|
||||
public struct Zip<UpstreamA: Publisher, UpstreamB: Publisher>: Publisher
|
||||
where UpstreamA.Failure == UpstreamB.Failure
|
||||
{
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (UpstreamA.Output, UpstreamB.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = UpstreamA.Failure
|
||||
|
||||
public let a: UpstreamA
|
||||
|
||||
public let b: UpstreamB
|
||||
|
||||
public init(_ a: UpstreamA, _ b: UpstreamB) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this
|
||||
/// `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream) where
|
||||
UpstreamB.Failure == Downstream.Failure,
|
||||
Downstream.Input == (UpstreamA.Output, UpstreamB.Output)
|
||||
{
|
||||
_ = Inner<Downstream>(downstream: subscriber, a, b)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the zip function to three upstream publishers.
|
||||
public struct Zip3<UpstreamA: Publisher,
|
||||
UpstreamB: Publisher,
|
||||
UpstreamC: Publisher>
|
||||
: Publisher
|
||||
where UpstreamA.Failure == UpstreamB.Failure,
|
||||
UpstreamB.Failure == UpstreamC.Failure
|
||||
{
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (UpstreamA.Output, UpstreamB.Output, UpstreamC.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = UpstreamA.Failure
|
||||
|
||||
public let a: UpstreamA
|
||||
|
||||
public let b: UpstreamB
|
||||
|
||||
public let c: UpstreamC
|
||||
|
||||
public init(_ a: UpstreamA, _ b: UpstreamB, _ c: UpstreamC) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this
|
||||
/// `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream>(subscriber: Downstream)
|
||||
where Downstream: Subscriber,
|
||||
UpstreamC.Failure == Downstream.Failure,
|
||||
Downstream.Input == (UpstreamA.Output, UpstreamB.Output, UpstreamC.Output)
|
||||
{
|
||||
_ = Inner<Downstream>(downstream: subscriber, a, b, c)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher created by applying the zip function to four upstream publishers.
|
||||
public struct Zip4<
|
||||
UpstreamA: Publisher,
|
||||
UpstreamB: Publisher,
|
||||
UpstreamC: Publisher,
|
||||
UpstreamD: Publisher
|
||||
>: Publisher where
|
||||
UpstreamA.Failure == UpstreamB.Failure,
|
||||
UpstreamB.Failure == UpstreamC.Failure,
|
||||
UpstreamC.Failure == UpstreamD.Failure
|
||||
{
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (
|
||||
UpstreamA.Output,
|
||||
UpstreamB.Output,
|
||||
UpstreamC.Output,
|
||||
UpstreamD.Output)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = UpstreamA.Failure
|
||||
|
||||
public let a: UpstreamA
|
||||
|
||||
public let b: UpstreamB
|
||||
|
||||
public let c: UpstreamC
|
||||
|
||||
public let d: UpstreamD
|
||||
|
||||
public init(_ a: UpstreamA, _ b: UpstreamB, _ c: UpstreamC, _ d: UpstreamD) {
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this
|
||||
/// `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where UpstreamD.Failure == Downstream.Failure,
|
||||
Downstream.Input == (
|
||||
UpstreamA.Output,
|
||||
UpstreamB.Output,
|
||||
UpstreamC.Output,
|
||||
UpstreamD.Output)
|
||||
{
|
||||
_ = Inner<Downstream>(downstream: subscriber, a, b, c, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Combine elements from another publisher and deliver pairs of elements as tuples.
|
||||
///
|
||||
/// The returned publisher waits until both publishers have emitted an event, then
|
||||
/// delivers the oldest unconsumed event from each publisher together as a tuple to
|
||||
/// the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2`
|
||||
/// emits event `c`, the zip publisher emits the tuple `(a, c)`. It won’t emit a
|
||||
/// tuple with event `b` until `P2` emits another event.
|
||||
/// If either upstream publisher finishes successfuly or fails with an error, the
|
||||
/// zipped publisher does the same.
|
||||
///
|
||||
/// - Parameter other: Another publisher.
|
||||
/// - Returns: A publisher that emits pairs of elements from the upstream publishers
|
||||
/// as tuples.
|
||||
public func zip<Other>(_ other: Other) -> Publishers.Zip<Self, Other>
|
||||
where Other: Publisher, Self.Failure == Other.Failure
|
||||
{
|
||||
return Publishers.Zip(self, other)
|
||||
}
|
||||
|
||||
/// Combine elements from another publisher and deliver a transformed output.
|
||||
///
|
||||
/// The returned publisher waits until both publishers have emitted an event, then
|
||||
/// delivers the oldest unconsumed event from each publisher together as a tuple to
|
||||
/// the subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2`
|
||||
/// emits event `c`, the zip publisher emits the tuple `(a, c)`. It won’t emit a tuple
|
||||
/// with event `b` until `P2` emits another event.
|
||||
/// If either upstream publisher finishes successfuly or fails with an error, the
|
||||
/// zipped publisher does the same.
|
||||
///
|
||||
/// - Parameter other: Another publisher.
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that emits pairs of elements from the upstream publishers
|
||||
/// as tuples.
|
||||
public func zip<Other, Result>(
|
||||
_ other: Other,
|
||||
_ transform: @escaping (Self.Output, Other.Output) -> Result)
|
||||
-> Publishers.Map<Publishers.Zip<Self, Other>, Result>
|
||||
where Other: Publisher, Self.Failure == Other.Failure
|
||||
{
|
||||
return Publishers.Map(upstream: Publishers.Zip(self, other), transform: transform)
|
||||
}
|
||||
|
||||
/// Combine elements from two other publishers and deliver groups of elements as
|
||||
/// tuples.
|
||||
///
|
||||
/// The returned publisher waits until all three publishers have emitted an event,
|
||||
/// then delivers the oldest unconsumed event from each publisher as a tuple to the
|
||||
/// subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2`
|
||||
/// emits elements `c` and `d`, and publisher `P3` emits the event `e`, the zip
|
||||
/// publisher emits the tuple `(a, c, e)`. It won’t emit a tuple with elements `b` or
|
||||
/// `d` until `P3` emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped
|
||||
/// publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers
|
||||
/// as tuples.
|
||||
public func zip<Other1, Other2>(_ publisher1: Other1, _ publisher2: Other2)
|
||||
-> Publishers.Zip3<Self, Other1, Other2>
|
||||
where Other1: Publisher,
|
||||
Other2: Publisher,
|
||||
Self.Failure == Other1.Failure,
|
||||
Other1.Failure == Other2.Failure
|
||||
{
|
||||
return Publishers.Zip3(self, publisher1, publisher2)
|
||||
}
|
||||
|
||||
/// Combine elements from two other publishers and deliver a transformed output.
|
||||
///
|
||||
/// The returned publisher waits until all three publishers have emitted an event,
|
||||
/// then delivers the oldest unconsumed event from each publisher as a tuple to the
|
||||
/// subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2`
|
||||
/// emits elements `c` and `d`, and publisher `P3` emits the event `e`, the zip
|
||||
/// publisher emits the tuple `(a, c, e)`. It won’t emit a tuple with elements `b` or
|
||||
/// `d` until `P3` emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped
|
||||
/// publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers
|
||||
/// as tuples.
|
||||
public func zip<Other1, Other2, Result>(
|
||||
_ publisher1: Other1,
|
||||
_ publisher2: Other2,
|
||||
_ transform: @escaping (Self.Output, Other1.Output, Other2.Output) -> Result)
|
||||
-> Publishers.Map<Publishers.Zip3<Self, Other1, Other2>, Result>
|
||||
where Other1: Publisher,
|
||||
Other2: Publisher,
|
||||
Self.Failure == Other1.Failure,
|
||||
Other1.Failure == Other2.Failure
|
||||
{
|
||||
return Publishers.Map(upstream: Publishers.Zip3(self, publisher1, publisher2),
|
||||
transform: transform)
|
||||
}
|
||||
|
||||
/// Combine elements from three other publishers and deliver groups of elements as
|
||||
/// tuples.
|
||||
///
|
||||
/// The returned publisher waits until all four publishers have emitted an event, then
|
||||
/// delivers the oldest unconsumed event from each publisher as a tuple to the
|
||||
/// subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2`
|
||||
/// emits elements `c` and `d`, and publisher `P3` emits the elements `e` and `f`, and
|
||||
/// publisher `P4` emits the event `g`, the zip publisher emits the tuple
|
||||
/// `(a, c, e, g)`. It won’t emit a tuple with elements `b`, `d`, or `f` until `P4`
|
||||
/// emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped
|
||||
/// publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - publisher3: A fourth publisher.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers
|
||||
/// as tuples.
|
||||
public func zip<Other1, Other2, Other3>(_ publisher1: Other1,
|
||||
_ publisher2: Other2,
|
||||
_ publisher3: Other3)
|
||||
-> Publishers.Zip4<Self, Other1, Other2, Other3>
|
||||
where Other1: Publisher,
|
||||
Other2: Publisher,
|
||||
Other3: Publisher,
|
||||
Self.Failure == Other1.Failure,
|
||||
Other1.Failure == Other2.Failure,
|
||||
Other2.Failure == Other3.Failure
|
||||
{
|
||||
return Publishers.Zip4(self, publisher1, publisher2, publisher3)
|
||||
}
|
||||
|
||||
/// Combine elements from three other publishers and deliver a transformed output.
|
||||
///
|
||||
/// The returned publisher waits until all four publishers have emitted an event, then
|
||||
/// delivers the oldest unconsumed event from each publisher as a tuple to the
|
||||
/// subscriber.
|
||||
/// For example, if publisher `P1` emits elements `a` and `b`, and publisher `P2`
|
||||
/// emits elements `c` and `d`, and publisher `P3` emits the elements `e` and `f`, and
|
||||
/// publisher `P4` emits the event `g`, the zip publisher emits the tuple
|
||||
/// `(a, c, e, g)`. It won’t emit a tuple with elements `b`, `d`, or `f` until `P4`
|
||||
/// emits another event.
|
||||
/// If any upstream publisher finishes successfuly or fails with an error, the zipped
|
||||
/// publisher does the same.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - publisher1: A second publisher.
|
||||
/// - publisher2: A third publisher.
|
||||
/// - publisher3: A fourth publisher.
|
||||
/// - transform: A closure that receives the most recent value from each publisher
|
||||
/// and returns a new value to publish.
|
||||
/// - Returns: A publisher that emits groups of elements from the upstream publishers
|
||||
/// as tuples.
|
||||
public func zip<Other1, Other2, Other3, Result>(
|
||||
_ publisher1: Other1,
|
||||
_ publisher2: Other2,
|
||||
_ publisher3: Other3,
|
||||
_ transform: @escaping (Self.Output, Other1.Output, Other2.Output, Other3.Output)
|
||||
-> Result)
|
||||
-> Publishers.Map<Publishers.Zip4<Self, Other1, Other2, Other3>, Result>
|
||||
where Other1: Publisher,
|
||||
Other2: Publisher,
|
||||
Other3: Publisher,
|
||||
Self.Failure == Other1.Failure,
|
||||
Other1.Failure == Other2.Failure,
|
||||
Other2.Failure == Other3.Failure
|
||||
{
|
||||
return Publishers.Map(upstream: Publishers.Zip4(self,
|
||||
publisher1,
|
||||
publisher2,
|
||||
publisher3),
|
||||
transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Zip {
|
||||
private class Inner<Downstream: Subscriber>: InnerBase<Downstream>
|
||||
where Downstream.Failure == Failure,
|
||||
Downstream.Input == (UpstreamA.Output, UpstreamB.Output)
|
||||
{
|
||||
private lazy var aSubscriber = ChildSubscriber<UpstreamA, Downstream>(self, 0)
|
||||
private lazy var bSubscriber = ChildSubscriber<UpstreamB, Downstream>(self, 1)
|
||||
|
||||
init(downstream: Downstream, _ a: UpstreamA, _ b: UpstreamB) {
|
||||
super.init(downstream: downstream)
|
||||
|
||||
a.subscribe(aSubscriber)
|
||||
b.subscribe(bSubscriber)
|
||||
}
|
||||
|
||||
override fileprivate var upstreamSubscriptions: [ChildSubscription] {
|
||||
return [aSubscriber, bSubscriber]
|
||||
}
|
||||
|
||||
override fileprivate func dequeueValue() -> Downstream.Input {
|
||||
return (aSubscriber.dequeueValue(), bSubscriber.dequeueValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Zip3 {
|
||||
private class Inner<Downstream: Subscriber>: InnerBase<Downstream>
|
||||
where Downstream.Failure == Failure,
|
||||
Downstream.Input == (UpstreamA.Output, UpstreamB.Output, UpstreamC.Output)
|
||||
{
|
||||
private lazy var aSubscriber = ChildSubscriber<UpstreamA, Downstream>(self, 0)
|
||||
private lazy var bSubscriber = ChildSubscriber<UpstreamB, Downstream>(self, 1)
|
||||
private lazy var cSubscriber = ChildSubscriber<UpstreamC, Downstream>(self, 2)
|
||||
|
||||
init(downstream: Downstream, _ a: UpstreamA, _ b: UpstreamB, _ c: UpstreamC) {
|
||||
super.init(downstream: downstream)
|
||||
|
||||
a.subscribe(aSubscriber)
|
||||
b.subscribe(bSubscriber)
|
||||
c.subscribe(cSubscriber)
|
||||
}
|
||||
|
||||
override fileprivate var upstreamSubscriptions: [ChildSubscription] {
|
||||
return [aSubscriber, bSubscriber, cSubscriber]
|
||||
}
|
||||
|
||||
override fileprivate func dequeueValue() -> Downstream.Input {
|
||||
return (aSubscriber.dequeueValue(),
|
||||
bSubscriber.dequeueValue(),
|
||||
cSubscriber.dequeueValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Zip4 {
|
||||
private class Inner<Downstream: Subscriber>: InnerBase<Downstream>
|
||||
where Downstream.Failure == Failure,
|
||||
Downstream.Input == (
|
||||
UpstreamA.Output,
|
||||
UpstreamB.Output,
|
||||
UpstreamC.Output,
|
||||
UpstreamD.Output)
|
||||
{
|
||||
private lazy var aSubscriber = ChildSubscriber<UpstreamA, Downstream>(self, 0)
|
||||
private lazy var bSubscriber = ChildSubscriber<UpstreamB, Downstream>(self, 1)
|
||||
private lazy var cSubscriber = ChildSubscriber<UpstreamC, Downstream>(self, 2)
|
||||
private lazy var dSubscriber = ChildSubscriber<UpstreamD, Downstream>(self, 3)
|
||||
|
||||
init(downstream: Downstream,
|
||||
_ a: UpstreamA,
|
||||
_ b: UpstreamB,
|
||||
_ c: UpstreamC,
|
||||
_ d: UpstreamD)
|
||||
{
|
||||
super.init(downstream: downstream)
|
||||
|
||||
a.subscribe(aSubscriber)
|
||||
b.subscribe(bSubscriber)
|
||||
c.subscribe(cSubscriber)
|
||||
d.subscribe(dSubscriber)
|
||||
}
|
||||
|
||||
override fileprivate var upstreamSubscriptions: [ChildSubscription] {
|
||||
return [aSubscriber, bSubscriber, cSubscriber, dSubscriber]
|
||||
}
|
||||
|
||||
override fileprivate func dequeueValue() -> Downstream.Input {
|
||||
return (aSubscriber.dequeueValue(),
|
||||
bSubscriber.dequeueValue(),
|
||||
cSubscriber.dequeueValue(),
|
||||
dSubscriber.dequeueValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InnerBase<Downstream: Subscriber>: CustomStringConvertible {
|
||||
let description = "Zip"
|
||||
|
||||
private let lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private let downstream: Downstream
|
||||
private var downstreamDemand = Subscribers.Demand.none
|
||||
private var valueIsBeingProcessed = false
|
||||
private var value: Downstream.Input?
|
||||
private var isFinished = false
|
||||
|
||||
// The following two pieces of state are a hacky implementation of subtle Apple
|
||||
// concurrency behaviors. Specifically, when Zip is processing an upstream child value
|
||||
// and sending a resulting value downstream, multiple behaviors are changed.
|
||||
// 1. If a downstream demand request comes in during this period, the demand request
|
||||
// for that specific triggering upstream child will be communiated via the result
|
||||
// of `.receive(_ input:)` INSTEAD of a later `.request(_ demand:)` call.
|
||||
// (AppleRef: 001)
|
||||
// 2. If an upstream `.finished` comes in during this time period, the "finished
|
||||
// asssessment check" (AppleRef: 002) is skipped.
|
||||
// If an upstream value is being processed when a downstream demand request comes in,
|
||||
// the demand for that specfic upstream child will be communiated via the result
|
||||
// of `.receive(_ input:)` INSTEAD of a later `.request(_ demand:)` call.
|
||||
private final var processingValueForChild: ChildSubscription?
|
||||
private final var demandReceivedWhileProcessing: Subscribers.Demand?
|
||||
|
||||
init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
fileprivate var upstreamSubscriptions: [ChildSubscription] {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
fileprivate func dequeueValue() -> Downstream.Input {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
fileprivate final func receivedSubscription(for child: ChildSubscription) {
|
||||
lock.lock()
|
||||
child.state = .active
|
||||
let sendSubscriptionDownstream = upstreamSubscriptions
|
||||
.filter { $0.state == .waitingForSubscription }
|
||||
.isEmpty
|
||||
lock.unlock()
|
||||
|
||||
if sendSubscriptionDownstream {
|
||||
self.sendSubscriptionDownstream()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final func receivedChildValue(
|
||||
child: ChildSubscription,
|
||||
_ lockedStoreValue: () -> Void
|
||||
) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
lockedStoreValue()
|
||||
defer {
|
||||
checkShouldFinish()
|
||||
lock.unlock()
|
||||
}
|
||||
if let dequeuedValue = maybeDequeueValue() {
|
||||
value = dequeuedValue
|
||||
assert(processingValueForChild == nil)
|
||||
processingValueForChild = child
|
||||
valueIsBeingProcessed = true
|
||||
return processValue() ?? .none
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final func receivedCompletion(
|
||||
_ completion: Subscribers.Completion<Downstream.Failure>,
|
||||
forChild child: ChildSubscription)
|
||||
{
|
||||
switch completion {
|
||||
case .failure:
|
||||
downstream.receive(completion: completion)
|
||||
lock.lock()
|
||||
child.state = .failed
|
||||
let subscriptionsToCancel = upstreamSubscriptions
|
||||
lock.unlock()
|
||||
subscriptionsToCancel.forEach { $0.cancel() }
|
||||
case .finished:
|
||||
lock.lock()
|
||||
child.state = .finished
|
||||
if !valueIsBeingProcessed {
|
||||
valueIsBeingProcessed = true
|
||||
if processingValueForChild == nil &&
|
||||
!areMoreValuesPossible &&
|
||||
!isFinished {
|
||||
sendFinishDownstream()
|
||||
} else {
|
||||
processValue()
|
||||
}
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkShouldFinish() {
|
||||
if processingValueForChild == nil && upstreamSubscriptions.shouldFinish() {
|
||||
sendFinishDownstream()
|
||||
isFinished = true
|
||||
}
|
||||
}
|
||||
|
||||
private func maybeDequeueValue() -> Downstream.Input? {
|
||||
return hasCompleteValueAvailable ? dequeueValue() : nil
|
||||
}
|
||||
|
||||
private func sendSubscriptionDownstream() {
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
private var hasCompleteValueAvailable: Bool {
|
||||
return upstreamSubscriptions.allSatisfy { $0.hasValue }
|
||||
}
|
||||
|
||||
private var areMoreValuesPossible: Bool {
|
||||
// More values are possible if all children are (active || have surplus)
|
||||
return upstreamSubscriptions
|
||||
.allSatisfy { $0.state == .active || $0.hasValue }
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func processValue() -> Subscribers.Demand? {
|
||||
assert(valueIsBeingProcessed)
|
||||
|
||||
lock.lock()
|
||||
defer {
|
||||
valueIsBeingProcessed = false
|
||||
processingValueForChild = nil
|
||||
demandReceivedWhileProcessing = nil
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
if let value = self.value {
|
||||
if downstreamDemand != .none {
|
||||
downstreamDemand -= 1
|
||||
}
|
||||
let newDemand = downstream.receive(value)
|
||||
if newDemand != .none {
|
||||
downstreamDemand += newDemand
|
||||
demandReceivedWhileProcessing = newDemand
|
||||
}
|
||||
self.value = nil
|
||||
}
|
||||
|
||||
return demandReceivedWhileProcessing
|
||||
}
|
||||
|
||||
private func sendRequestUpstream(demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
let subscriptionsToRequest = upstreamSubscriptions
|
||||
.filter { $0.childIndex != processingValueForChild?.childIndex }
|
||||
lock.unlock()
|
||||
subscriptionsToRequest.forEach { $0.request(demand) }
|
||||
}
|
||||
|
||||
private func sendFinishDownstream() {
|
||||
downstream.receive(completion: .finished)
|
||||
lock.lock()
|
||||
let activeChildren = upstreamSubscriptions.filter { $0.state == .active }
|
||||
lock.unlock()
|
||||
activeChildren.forEach { $0.cancel() }
|
||||
}
|
||||
}
|
||||
|
||||
extension InnerBase: Subscription {
|
||||
fileprivate final func request(_ demand: Subscribers.Demand) {
|
||||
guard demand != .none else {
|
||||
fatalError()
|
||||
}
|
||||
lock.lock()
|
||||
downstreamDemand += demand
|
||||
sendRequestUpstream(demand: demand)
|
||||
if valueIsBeingProcessed {
|
||||
demandReceivedWhileProcessing = demand
|
||||
} else {
|
||||
valueIsBeingProcessed = true
|
||||
processValue()
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
fileprivate final func cancel() {
|
||||
lock.lock()
|
||||
let subscriptionsToCancel = upstreamSubscriptions
|
||||
lock.unlock()
|
||||
subscriptionsToCancel.forEach { $0.cancel() }
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == ChildSubscription {
|
||||
func shouldFinish() -> Bool {
|
||||
for subscription in self
|
||||
where subscription.state == .finished && !subscription.hasValue{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChildState {
|
||||
case waitingForSubscription
|
||||
case active
|
||||
case finished
|
||||
case failed
|
||||
case canceled
|
||||
}
|
||||
|
||||
// Note that it's critical that this protocol not have any associated types - specifically
|
||||
// note that it does not refer to `Upstream`.
|
||||
// This allows `InnerBase` to do most of the heavy lifting without regard to the
|
||||
// upstream publisher's value type.
|
||||
private protocol ChildSubscription: AnyObject, Subscription {
|
||||
var state: ChildState { get set }
|
||||
var childIndex: Int { get }
|
||||
var hasValue: Bool { get }
|
||||
}
|
||||
|
||||
private final class ChildSubscriber<Upstream: Publisher, Downstream: Subscriber>
|
||||
where Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
fileprivate final var state: ChildState = .waitingForSubscription
|
||||
fileprivate final var upstreamSubscription: Subscription?
|
||||
private var values = [Upstream.Output]()
|
||||
private unowned let parent: InnerBase<Downstream>
|
||||
fileprivate let childIndex: Int
|
||||
|
||||
init(_ parent: InnerBase<Downstream>, _ childIndex: Int) {
|
||||
self.parent = parent
|
||||
self.childIndex = childIndex
|
||||
}
|
||||
|
||||
fileprivate final func dequeueValue() -> Upstream.Output {
|
||||
return values.remove(at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChildSubscriber: ChildSubscription {
|
||||
fileprivate final var hasValue: Bool {
|
||||
return !values.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
extension ChildSubscriber: Subscription {
|
||||
fileprivate final func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChildSubscriber: Cancellable {
|
||||
fileprivate final func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ChildSubscriber: Subscriber {
|
||||
fileprivate final func receive(subscription: Subscription) {
|
||||
if upstreamSubscription == nil {
|
||||
upstreamSubscription = subscription
|
||||
parent.receivedSubscription(for: self)
|
||||
} else {
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return parent.receivedChildValue(child: self) { values.append(input) }
|
||||
}
|
||||
|
||||
fileprivate final func receive(completion: Subscribers.Completion<Failure>) {
|
||||
parent.receivedCompletion(completion, forChild: self)
|
||||
}
|
||||
}
|
||||
@@ -136,8 +136,7 @@ extension Result.OCombine {
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
guard let downstream = self.downstream.take() else { return }
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
${template_header}
|
||||
//
|
||||
// RootProtocols.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
%{
|
||||
variants = [(True, '#if compiler(>=5.7)'), (False, '#else')]
|
||||
}%
|
||||
% for primary_associated_types_supported, guard in variants:
|
||||
${guard}
|
||||
/// 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>' if primary_associated_types_supported else ''} {
|
||||
|
||||
/// 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>' if primary_associated_types_supported else ''}: 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>' if primary_associated_types_supported else ''}: 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>' if primary_associated_types_supported else ''}: 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>' if primary_associated_types_supported else ''} {
|
||||
|
||||
/// 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
|
||||
}
|
||||
% end
|
||||
#endif
|
||||
@@ -29,51 +29,6 @@ public protocol SchedulerTimeIntervalConvertible {
|
||||
static func nanoseconds(_ ns: Int) -> Self
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
extension Scheduler {
|
||||
|
||||
/// Performs the action at some time after the specified date, using the scheduler’s
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Subject+Void.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
extension Subject where Output == Void {
|
||||
|
||||
/// Sends a void value to the subscriber.
|
||||
///
|
||||
/// Use `Void` inputs and outputs when you want to signal that an event has occurred,
|
||||
/// but don’t need to send the event itself.
|
||||
public func send() {
|
||||
send(())
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
//
|
||||
// Subject.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
extension Subject where Output == Void {
|
||||
|
||||
/// Sends a void value to the subscriber.
|
||||
///
|
||||
/// Use `Void` inputs and outputs when you want to signal that an event has occurred,
|
||||
/// but don’t need to send the event itself.
|
||||
public func send() {
|
||||
send(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Subscriber+Void.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
extension Subscriber where Input == Void {
|
||||
|
||||
/// Tells the subscriber that a publisher of void elements is ready to receive further
|
||||
/// requests.
|
||||
///
|
||||
/// Use `Void` inputs and outputs when you want to signal that an event has occurred,
|
||||
/// but don’t need to send the event itself.
|
||||
/// - Returns: A `Subscribers.Demand` instance indicating how many more elements
|
||||
/// the subscriber expects to receive.
|
||||
public func receive() -> Subscribers.Demand {
|
||||
return receive(())
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
//
|
||||
// Subscriber.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// 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>)
|
||||
}
|
||||
|
||||
extension Subscriber where Input == Void {
|
||||
|
||||
/// Tells the subscriber that a publisher of void elements is ready to receive further
|
||||
/// requests.
|
||||
///
|
||||
/// Use `Void` inputs and outputs when you want to signal that an event has occurred,
|
||||
/// but don’t need to send the event itself.
|
||||
/// - Returns: A `Subscribers.Demand` instance indicating how many more elements
|
||||
/// the subscriber expects to receive.
|
||||
public func receive() -> Subscribers.Demand {
|
||||
return receive(())
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,17 @@ extension Subscribers {
|
||||
lock.assertOwner()
|
||||
#endif
|
||||
status = .terminal
|
||||
object = nil
|
||||
lock.unlock()
|
||||
|
||||
// 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(object) {
|
||||
object = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
extension Subscribers {
|
||||
|
||||
/// A signal that a publisher doesn’t produce additional elements, either due to
|
||||
@@ -23,6 +27,10 @@ extension Subscribers.Completion: Equatable where Failure: Equatable {}
|
||||
|
||||
extension Subscribers.Completion: Hashable where Failure: Hashable {}
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)
|
||||
extension Subscribers.Completion: Sendable {}
|
||||
#endif
|
||||
|
||||
extension Subscribers.Completion {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case success = "success"
|
||||
@@ -70,4 +78,13 @@ extension Subscribers.Completion {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
internal var failure: Failure? {
|
||||
switch self {
|
||||
case .finished:
|
||||
return nil
|
||||
case .failure(let failure):
|
||||
return failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
// swiftlint:disable shorthand_operator - because of false positives here
|
||||
// swiftlint:disable attributes
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
extension Subscribers {
|
||||
|
||||
@@ -466,3 +470,7 @@ extension Subscribers {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)
|
||||
extension Subscribers.Demand: Sendable {}
|
||||
#endif
|
||||
|
||||
@@ -73,8 +73,22 @@ extension Subscribers {
|
||||
|
||||
public func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
status = .terminal
|
||||
let receiveCompletion = self.receiveCompletion
|
||||
terminateAndConsumeLock()
|
||||
self.receiveCompletion = { _ in }
|
||||
|
||||
// We MUST release the closures AFTER unlocking the lock,
|
||||
// since releasing a closure may trigger execution of arbitrary code,
|
||||
// for example, if the closure captures an object with a deinit.
|
||||
// When closure deallocates, the object's deinit is called, and holding
|
||||
// the lock at that moment can lead to deadlocks.
|
||||
// See https://github.com/OpenCombine/OpenCombine/issues/208
|
||||
|
||||
withExtendedLifetime(receiveValue) {
|
||||
receiveValue = { _ in }
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
receiveCompletion(completion)
|
||||
}
|
||||
|
||||
@@ -84,18 +98,21 @@ extension Subscribers {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
terminateAndConsumeLock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
private func terminateAndConsumeLock() {
|
||||
#if DEBUG
|
||||
lock.assertOwner()
|
||||
#endif
|
||||
status = .terminal
|
||||
receiveValue = { _ in }
|
||||
receiveCompletion = { _ in }
|
||||
lock.unlock()
|
||||
|
||||
// We MUST release the closures AFTER unlocking the lock,
|
||||
// since releasing a closure may trigger execution of arbitrary code,
|
||||
// for example, if the closure captures an object with a deinit.
|
||||
// When closure deallocates, the object's deinit is called, and holding
|
||||
// the lock at that moment can lead to deadlocks.
|
||||
// See https://github.com/OpenCombine/OpenCombine/issues/208
|
||||
|
||||
withExtendedLifetime((receiveValue, receiveCompletion)) {
|
||||
receiveCompletion = { _ in }
|
||||
receiveValue = { _ in }
|
||||
lock.unlock()
|
||||
}
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Created by Sergej Jaskiewicz on 27.09.2020.
|
||||
//
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
public protocol _Introspection: AnyObject {
|
||||
|
||||
func willReceive<Upstream: Publisher, Downstream: Subscriber>(
|
||||
|
||||
@@ -50,13 +50,7 @@ extension DispatchQueue {
|
||||
/// - Parameter other: Another dispatch queue time.
|
||||
/// - Returns: The time interval between this time and the provided time.
|
||||
public func distance(to other: SchedulerTimeType) -> Stride {
|
||||
let start = dispatchTime.rawValue
|
||||
let end = other.dispatchTime.rawValue
|
||||
return .nanoseconds(
|
||||
end >= start
|
||||
? Int(Int64(bitPattern: end) - Int64(bitPattern: start))
|
||||
: -Int(Int64(bitPattern: start) - Int64(bitPattern: end))
|
||||
)
|
||||
return Stride(dispatchTime.polyfillDistance(to: other.dispatchTime))
|
||||
}
|
||||
|
||||
/// Returns a dispatch queue scheduler time calculated by advancing
|
||||
@@ -104,8 +98,17 @@ extension DispatchQueue {
|
||||
/// value of the conforming type.
|
||||
public typealias Magnitude = Int
|
||||
|
||||
private var _nanoseconds: Int64
|
||||
|
||||
/// The value of this time interval in nanoseconds.
|
||||
public var magnitude: Int
|
||||
public var magnitude: Int {
|
||||
get {
|
||||
return Int(_nanoseconds)
|
||||
}
|
||||
set {
|
||||
_nanoseconds = Int64(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `DispatchTimeInterval` created with the value of this type
|
||||
/// in nanoseconds.
|
||||
@@ -113,8 +116,8 @@ extension DispatchQueue {
|
||||
return .nanoseconds(magnitude)
|
||||
}
|
||||
|
||||
private init(magnitude: Int) {
|
||||
self.magnitude = magnitude
|
||||
private init(magnitude: Int64) {
|
||||
_nanoseconds = magnitude
|
||||
}
|
||||
|
||||
/// Creates a dispatch queue time interval from the given
|
||||
@@ -200,11 +203,11 @@ extension DispatchQueue {
|
||||
/// - Parameter exactly: A binary integer representing a time interval.
|
||||
public init?<Source: BinaryInteger>(exactly source: Source) {
|
||||
guard let value = Int(exactly: source) else { return nil }
|
||||
self = .nanoseconds(value)
|
||||
self = .seconds(value)
|
||||
}
|
||||
|
||||
public static func < (lhs: Stride, rhs: Stride) -> Bool {
|
||||
return lhs.magnitude < rhs.magnitude
|
||||
return lhs._nanoseconds < rhs._nanoseconds
|
||||
}
|
||||
|
||||
public static func * (lhs: Stride, rhs: Stride) -> Stride {
|
||||
@@ -212,43 +215,51 @@ extension DispatchQueue {
|
||||
}
|
||||
|
||||
public static func + (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(magnitude: lhs.magnitude + rhs.magnitude)
|
||||
return Stride(magnitude: lhs._nanoseconds + rhs._nanoseconds)
|
||||
}
|
||||
|
||||
public static func - (lhs: Stride, rhs: Stride) -> Stride {
|
||||
return Stride(magnitude: lhs.magnitude - rhs.magnitude)
|
||||
return Stride(magnitude: lhs._nanoseconds - rhs._nanoseconds)
|
||||
}
|
||||
|
||||
public static func -= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude -= rhs.magnitude
|
||||
lhs._nanoseconds -= rhs._nanoseconds
|
||||
}
|
||||
|
||||
public static func *= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude = 0
|
||||
lhs._nanoseconds = 0
|
||||
}
|
||||
|
||||
public static func += (lhs: inout Stride, rhs: Stride) {
|
||||
lhs.magnitude += rhs.magnitude
|
||||
lhs._nanoseconds += rhs._nanoseconds
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Double) -> Stride {
|
||||
return Stride(magnitude: Int(value * 1_000_000_000))
|
||||
let nanoseconds = value * 1_000_000_000
|
||||
if nanoseconds >= Double(Int64.max) {
|
||||
return Stride(magnitude: .max)
|
||||
}
|
||||
if nanoseconds <= Double(Int64.min) {
|
||||
return Stride(magnitude: .min)
|
||||
}
|
||||
return Stride(magnitude: Int64(nanoseconds))
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: clampedIntProduct(value, 1_000_000_000))
|
||||
return Stride(magnitude: clampedIntProduct(Int64(value),
|
||||
1_000_000_000))
|
||||
}
|
||||
|
||||
public static func milliseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: clampedIntProduct(value, 1_000_000))
|
||||
return Stride(magnitude: clampedIntProduct(Int64(value), 1_000_000))
|
||||
}
|
||||
|
||||
public static func microseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: clampedIntProduct(value, 1_000))
|
||||
return Stride(magnitude: clampedIntProduct(Int64(value), 1_000))
|
||||
}
|
||||
|
||||
public static func nanoseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value)
|
||||
return Stride(magnitude: Int64(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,10 +398,10 @@ extension DispatchQueue: OpenCombine.Scheduler {
|
||||
// This function is taken from swift-corelibs-libdispatch:
|
||||
// https://github.com/apple/swift-corelibs-libdispatch/blob/c992dacf3ca114806e6ac9ffc9113b19255be9fe/src/swift/Time.swift#L134-L144
|
||||
//
|
||||
// Returns m1 * m2, clamped to the range [Int.min, Int.max].
|
||||
// Returns m1 * m2, clamped to the range [Int64.min, Int64.max].
|
||||
// Because of the way this function is used, we can always assume
|
||||
// that m2 > 0.
|
||||
private func clampedIntProduct(_ lhs: Int, _ rhs: Int) -> Int {
|
||||
private func clampedIntProduct(_ lhs: Int64, _ rhs: Int64) -> Int64 {
|
||||
assert(rhs > 0, "multiplier must be positive")
|
||||
let (result, overflow) = lhs.multipliedReportingOverflow(by: rhs)
|
||||
if overflow {
|
||||
@@ -398,3 +409,31 @@ private func clampedIntProduct(_ lhs: Int, _ rhs: Int) -> Int {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
extension DispatchTime {
|
||||
|
||||
fileprivate func polyfillDistance(to other: DispatchTime) -> DispatchTimeInterval {
|
||||
#if canImport(Darwin) && compiler(>=5.1)
|
||||
if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) {
|
||||
return distance(to: other)
|
||||
}
|
||||
#endif
|
||||
let start = rawValue
|
||||
let end = other.rawValue
|
||||
if end >= start {
|
||||
let result = end &- start
|
||||
if result > UInt64(Int.max) {
|
||||
return .never
|
||||
} else {
|
||||
return .nanoseconds(Int(result))
|
||||
}
|
||||
} else {
|
||||
let result = start &- end
|
||||
if result > UInt64(Int.max) {
|
||||
return .never
|
||||
} else {
|
||||
return .nanoseconds(-Int(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
// Created by Sergej Jaskiewicz on 28.10.2020.
|
||||
//
|
||||
|
||||
#if canImport(CoreFoundation)
|
||||
import CoreFoundation
|
||||
#endif
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Use CoreFoundation on Darwin, since some pure
|
||||
@@ -60,7 +63,12 @@ internal struct Timer {
|
||||
repeats: Bool,
|
||||
block: @escaping (Timer) -> Void
|
||||
) {
|
||||
self.init(fire: Date(), interval: timeInterval, repeats: repeats, block: block)
|
||||
self.init(
|
||||
fire: Date() + timeInterval,
|
||||
interval: timeInterval,
|
||||
repeats: repeats,
|
||||
block: block
|
||||
)
|
||||
}
|
||||
|
||||
internal var tolerance: TimeInterval {
|
||||
@@ -88,6 +96,7 @@ internal struct Timer {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if canImport(CoreFoundation)
|
||||
fileprivate func getCFRunLoopTimer() -> CFRunLoopTimer? {
|
||||
#if canImport(Darwin)
|
||||
return underlyingTimer
|
||||
@@ -108,6 +117,7 @@ internal struct Timer {
|
||||
fatalError("unreachable")
|
||||
#endif
|
||||
}
|
||||
#endif // canImport(CoreFoundation)
|
||||
}
|
||||
|
||||
extension RunLoop {
|
||||
@@ -133,6 +143,7 @@ extension RunLoop {
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(CoreFoundation)
|
||||
extension RunLoop.Mode {
|
||||
fileprivate func asCFRunLoopMode() -> CFRunLoopMode {
|
||||
#if canImport(Darwin)
|
||||
@@ -154,3 +165,4 @@ extension RunLoop.Mode {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif // canImport(CoreFoundation)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Utils.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 28.08.2021.
|
||||
//
|
||||
|
||||
extension Optional {
|
||||
internal mutating func take() -> Optional {
|
||||
let taken = self
|
||||
self = nil
|
||||
return taken
|
||||
}
|
||||
}
|
||||
@@ -200,13 +200,13 @@ extension Notification {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard let center = self.center, let observation = self.observation else {
|
||||
guard let center = self.center.take(),
|
||||
let observation = self.observation.take()
|
||||
else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
self.center = nil
|
||||
self.object = nil
|
||||
self.observation = nil
|
||||
lock.unlock()
|
||||
center.removeObserver(observation)
|
||||
}
|
||||
|
||||
@@ -234,19 +234,16 @@ extension OperationQueue {
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard let action = self.action else { return }
|
||||
self.action = nil
|
||||
guard let action = self.action.take() else { return }
|
||||
action()
|
||||
|
||||
guard let queue = self.queue,
|
||||
let context = self.context
|
||||
guard let queue = self.queue.take(),
|
||||
let context = self.context.take()
|
||||
else {
|
||||
self.queue = nil
|
||||
self.context = nil
|
||||
return
|
||||
}
|
||||
self.queue = nil
|
||||
self.context = nil
|
||||
|
||||
context.lock.lock()
|
||||
if context.operation == nil {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 13.12.2019.
|
||||
//
|
||||
|
||||
import CoreFoundation
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Created by Sergej Jaskiewicz on 23.06.2020.
|
||||
//
|
||||
|
||||
import CoreFoundation
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
@@ -203,11 +202,10 @@ extension Foundation.Timer {
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
if downstream == nil {
|
||||
if downstream.take() == nil {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstream = nil
|
||||
lock.unlock()
|
||||
parent?.disconnect(combineIdentifier)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#if canImport(Combine)
|
||||
@_exported import Combine
|
||||
#else
|
||||
@_exported import OpenCombine
|
||||
#if canImport(OpenCombineDispatch)
|
||||
@_exported import OpenCombineDispatch
|
||||
#endif
|
||||
#if canImport(OpenCombineFoundation)
|
||||
@_exported import OpenCombineFoundation
|
||||
#endif
|
||||
#endif
|
||||
@@ -5,4 +5,4 @@ disabled_rules:
|
||||
- explicit_acl
|
||||
- explicit_top_level_acl
|
||||
- explicit_enum_raw_value
|
||||
|
||||
- untyped_error_in_catch
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// FutureConcurrencyTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 12.12.2021.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if canImport(_Concurrency) && compiler(>=5.5)
|
||||
import _Concurrency
|
||||
#endif
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
// swiftlint:disable:next line_length
|
||||
#if !os(Windows) && !WASI && (canImport(_Concurrency) && compiler(>=5.5) || compiler(>=5.5.1)) // TEST_DISCOVERY_CONDITION
|
||||
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
|
||||
final class FutureConcurrencyTests: XCTestCase {
|
||||
|
||||
func testAsyncAwaitNonThrowingSuccess() async {
|
||||
var promise: Future<Int, Never>.Promise?
|
||||
let future = Future<Int, Never> { promise = $0 }
|
||||
|
||||
let task = Task {
|
||||
await future.value
|
||||
}
|
||||
|
||||
promise?(.success(42))
|
||||
|
||||
let value = await task.value
|
||||
|
||||
XCTAssertEqual(value, 42)
|
||||
}
|
||||
|
||||
func testAsyncAwaitThrowingSuccess() async throws {
|
||||
var promise: Future<Int, TestingError>.Promise?
|
||||
let future = Future<Int, TestingError> { promise = $0 }
|
||||
|
||||
let task = Task {
|
||||
try await future.value
|
||||
}
|
||||
|
||||
promise?(.success(42))
|
||||
|
||||
let value = try await task.value
|
||||
|
||||
XCTAssertEqual(value, 42)
|
||||
}
|
||||
|
||||
func testAsyncAwaitThrowingFailure() async throws {
|
||||
var promise: Future<Int, TestingError>.Promise?
|
||||
let future = Future<Int, TestingError> { promise = $0 }
|
||||
|
||||
let task = Task { try await future.value }
|
||||
|
||||
promise?(.failure(.oops))
|
||||
|
||||
do {
|
||||
_ = try await task.value
|
||||
XCTFail("Expected an error")
|
||||
} catch let error as TestingError {
|
||||
XCTAssertEqual(error, .oops)
|
||||
} catch {
|
||||
XCTFail("Unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -314,7 +314,7 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
receiveSubscription: { subscription in
|
||||
subscription.request(.unlimited)
|
||||
},
|
||||
receiveCompletion: { completion in
|
||||
receiveCompletion: { _ in
|
||||
cvs.send(completion: .failure("must not recurse"))
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 26.08.2019.
|
||||
//
|
||||
|
||||
#if !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Dispatch
|
||||
import XCTest
|
||||
|
||||
@@ -29,23 +31,34 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
uptimeNanoseconds: DispatchTime.distantFuture.uptimeNanoseconds - 1024
|
||||
)
|
||||
)
|
||||
let int64max = Scheduler.SchedulerTimeType(
|
||||
DispatchTime(
|
||||
uptimeNanoseconds: UInt64(Int.max)
|
||||
)
|
||||
)
|
||||
|
||||
XCTAssertEqual(time1.distance(to: time2), .nanoseconds(431))
|
||||
XCTAssertEqual(time2.distance(to: time1), .nanoseconds(-431))
|
||||
|
||||
XCTAssertEqual(time1.distance(to: distantFuture), .nanoseconds(-10001))
|
||||
XCTAssertEqual(distantFuture.distance(to: time1), .nanoseconds(10001))
|
||||
XCTAssertEqual(time2.distance(to: distantFuture), .nanoseconds(-10432))
|
||||
XCTAssertEqual(distantFuture.distance(to: time2), .nanoseconds(10432))
|
||||
XCTAssertEqual(time1.distance(to: distantFuture), .nanoseconds(.max))
|
||||
XCTAssertEqual(distantFuture.distance(to: time1), .nanoseconds(.max))
|
||||
XCTAssertEqual(time2.distance(to: distantFuture), .nanoseconds(.max))
|
||||
XCTAssertEqual(distantFuture.distance(to: time2), .nanoseconds(.max))
|
||||
|
||||
XCTAssertEqual(time1.distance(to: notSoDistantFuture), .nanoseconds(-11025))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time1), .nanoseconds(11025))
|
||||
XCTAssertEqual(time2.distance(to: notSoDistantFuture), .nanoseconds(-11456))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time2), .nanoseconds(11456))
|
||||
XCTAssertEqual(time1.distance(to: notSoDistantFuture), .nanoseconds(.max))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time1), .nanoseconds(.max))
|
||||
XCTAssertEqual(time2.distance(to: notSoDistantFuture), .nanoseconds(.max))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: time2), .nanoseconds(.max))
|
||||
|
||||
XCTAssertEqual(time1.distance(to: int64max), .nanoseconds(.max - 10000))
|
||||
XCTAssertEqual(int64max.distance(to: time1), .nanoseconds(-(.max - 10000)))
|
||||
XCTAssertEqual(time2.distance(to: int64max), .nanoseconds(.max - 10431))
|
||||
XCTAssertEqual(int64max.distance(to: time2), .nanoseconds(-(.max - 10431)))
|
||||
|
||||
XCTAssertEqual(distantFuture.distance(to: distantFuture), .nanoseconds(0))
|
||||
XCTAssertEqual(notSoDistantFuture.distance(to: notSoDistantFuture),
|
||||
.nanoseconds(0))
|
||||
XCTAssertEqual(int64max.distance(to: int64max), .nanoseconds(0))
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeAdvanced() {
|
||||
@@ -254,27 +267,25 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
XCTAssertEqual((2 as Stride).magnitude, 2_000_000_000)
|
||||
|
||||
XCTAssertNil(Stride(exactly: UInt64.max))
|
||||
XCTAssertEqual(Stride(exactly: 871 as UInt64)?.magnitude, 871)
|
||||
XCTAssertEqual(Stride(exactly: 2 as UInt64)?.magnitude, 2_000_000_000)
|
||||
}
|
||||
|
||||
func testStrideFromTooMuchSecondsCrashes() {
|
||||
assertCrashes {
|
||||
func testStrideFromTooMuchSeconds() {
|
||||
#if arch(x86_64) || arch(arm64) || arch(s390x) || arch(powerpc64) || arch(powerpc64le)
|
||||
// 64-bit platforms
|
||||
XCTAssertGreaterThan(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000).magnitude,
|
||||
.max
|
||||
)
|
||||
// 64-bit platforms
|
||||
XCTAssertEqual(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000).magnitude,
|
||||
.max
|
||||
)
|
||||
#elseif arch(i386) || arch(arm)
|
||||
// 32-bit platforms
|
||||
XCTAssertGreaterThan(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000 + 1).magnitude,
|
||||
.max
|
||||
)
|
||||
// 32-bit platforms
|
||||
XCTAssertEqual(
|
||||
Stride.seconds(Double(Int.max) / 1_000_000_000).magnitude,
|
||||
.max
|
||||
)
|
||||
#else
|
||||
#error("This architecture isn't known. Add it to the 32-bit or 64-bit line.")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideComparable() {
|
||||
@@ -441,7 +452,7 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
.encode(KeyedWrapper(value: stride))
|
||||
let encodedString = String(decoding: encodedData, as: UTF8.self)
|
||||
|
||||
XCTAssertEqual(encodedString, #"{"value":{"magnitude":419872}}"#)
|
||||
XCTAssertEqual(encodedString, #"{"value":{"_nanoseconds":419872}}"#)
|
||||
|
||||
let decodedStride = try decoder
|
||||
.decode(KeyedWrapper<Stride>.self, from: encodedData)
|
||||
@@ -484,11 +495,11 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
let main = expectation(description: "scheduled on main queue")
|
||||
main.assertForOverFulfill = true
|
||||
|
||||
var didExecuteMainAction = false
|
||||
let didExecuteMainAction = Atomic(false)
|
||||
let didExecuteBackgroundAction = Atomic(false)
|
||||
|
||||
mainScheduler.schedule {
|
||||
didExecuteMainAction = true
|
||||
didExecuteMainAction.set(true)
|
||||
main.fulfill()
|
||||
}
|
||||
|
||||
@@ -499,12 +510,14 @@ final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
didExecuteBackgroundAction.set(true)
|
||||
}
|
||||
|
||||
XCTAssertFalse(didExecuteMainAction, "action should be executed asynchronously")
|
||||
XCTAssertFalse(didExecuteMainAction.value,
|
||||
"action should be executed asynchronously")
|
||||
|
||||
// Wait for the background scheduler to execute the work.
|
||||
XCTAssertEqual(group.wait(timeout: .now() + 5.0), .success)
|
||||
|
||||
XCTAssertFalse(didExecuteMainAction, "action should be executed asynchronously")
|
||||
XCTAssertFalse(didExecuteMainAction.value,
|
||||
"action should be executed asynchronously")
|
||||
XCTAssertTrue(didExecuteBackgroundAction.value)
|
||||
|
||||
wait(for: [main], timeout: 0.1)
|
||||
@@ -570,7 +583,7 @@ private typealias Scheduler = DispatchQueue.OCombine
|
||||
private let mainScheduler = DispatchQueue.main.ocombine
|
||||
private let backgroundScheduler = DispatchQueue.global(qos: .background).ocombine
|
||||
|
||||
#endif
|
||||
#endif // OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
@@ -578,3 +591,5 @@ private typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
private struct KeyedWrapper<Value: Codable & Equatable>: Codable, Equatable {
|
||||
let value: Value
|
||||
}
|
||||
|
||||
#endif // !WASI
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
#if !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -61,3 +63,5 @@ final class JSONDecoderTests: XCTestCase {
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !WASI
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
#if !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -65,3 +67,5 @@ final class JSONEncoderTests: XCTestCase {
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !WASI
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
#if !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -630,4 +632,6 @@ extension Notification.Name {
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // !canImport(Darwin) && swift(<5.1)
|
||||
|
||||
#endif // !WASI
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// Created by Sergej Jaskiewicz on 14.06.2020.
|
||||
//
|
||||
|
||||
// OperationQueue has serious bugs in swift-corelibs-foundation prior to Swift 5.3.
|
||||
// (see https://github.com/apple/swift-corelibs-foundation/pull/2779)
|
||||
#if canImport(Darwin) || swift(>=5.3) && !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -15,10 +19,6 @@ import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
// OperationQueue has serious bugs in swift-corelibs-foundation prior to Swift 5.3.
|
||||
// (see https://github.com/apple/swift-corelibs-foundation/pull/2779)
|
||||
#if canImport(Darwin) || swift(>=5.3) // TEST_DISCOVERY_CONDITION
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class OperationQueueSchedulerTests: XCTestCase {
|
||||
|
||||
@@ -225,7 +225,7 @@ final class OperationQueueSchedulerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testScheduleRepeatingWithRealQueue() {
|
||||
let mainQueue = OperationQueue.main
|
||||
let mainQueueScheduler = makeScheduler(OperationQueue.main)
|
||||
|
||||
let expectation10ticks = expectation(description: "10 ticks")
|
||||
expectation10ticks.expectedFulfillmentCount = 10
|
||||
@@ -234,13 +234,12 @@ final class OperationQueueSchedulerTests: XCTestCase {
|
||||
|
||||
let ticks = Atomic([TimeInterval]())
|
||||
|
||||
let desiredDelay: TimeInterval = 0.7
|
||||
let desiredInterval: TimeInterval = 0.3
|
||||
let desiredDelay: TimeInterval = 0.8
|
||||
let desiredInterval: TimeInterval = 0.5
|
||||
|
||||
let cancellable = executeOnBackgroundThread { () -> Cancellable in
|
||||
let scheduler = makeScheduler(mainQueue)
|
||||
return scheduler
|
||||
.schedule(after: scheduler.now.advanced(by: .init(desiredDelay)),
|
||||
let cancellable = executeOnBackgroundThread {
|
||||
mainQueueScheduler
|
||||
.schedule(after: mainQueueScheduler.now.advanced(by: .init(desiredDelay)),
|
||||
interval: .init(desiredInterval)) {
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
ticks.append(Date().timeIntervalSinceReferenceDate)
|
||||
@@ -256,7 +255,7 @@ final class OperationQueueSchedulerTests: XCTestCase {
|
||||
RunLoop.main.run(until: Date() + 0.001)
|
||||
XCTAssertEqual(ticks.count, 0)
|
||||
|
||||
wait(for: [expectation10ticks], timeout: 5)
|
||||
wait(for: [expectation10ticks], timeout: 10)
|
||||
|
||||
if ticks.isEmpty {
|
||||
XCTFail("The scheduler doesn't work")
|
||||
@@ -332,6 +331,7 @@ extension OperationQueueScheduler.SchedulerTimeType.Stride
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension OperationQueueScheduler.SchedulerTimeType: DateBackedSchedulerTimeType {}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension OperationQueueScheduler: RunLoopLikeScheduler {}
|
||||
|
||||
private final class TestOperationQueue: OperationQueue {
|
||||
@@ -471,4 +471,4 @@ private final class TestOperationQueue: OperationQueue {
|
||||
#endif // canImport(Darwin)
|
||||
}
|
||||
|
||||
#endif // canImport(Darwin) || swift(>=5.3)
|
||||
#endif // canImport(Darwin) || swift(>=5.3) && !WASI
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
// PropertyListEncoder and PropertyListDecoder are unavailable in
|
||||
// swift-corelibs-foundation prior to Swift 5.1.
|
||||
#if canImport(Darwin) || swift(>=5.1) && !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -15,10 +19,6 @@ import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
// PropertyListEncoder and PropertyListDecoder are unavailable in
|
||||
// swift-corelibs-foundation prior to Swift 5.1.
|
||||
#if canImport(Darwin) || swift(>=5.1) // TEST_DISCOVERY_CONDITION
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PropertyListDecoderTests: XCTestCase {
|
||||
func testSuccessfullyDecode() {
|
||||
@@ -79,4 +79,4 @@ final class PropertyListDecoderTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
#endif // canImport(Darwin) || swift(>=5.1)
|
||||
#endif // canImport(Darwin) || swift(>=5.1) && !WASI
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// Created by Sergej Jaskiewicz on 10.12.2019.
|
||||
//
|
||||
|
||||
// PropertyListEncoder and PropertyListDecoder are unavailable in
|
||||
// swift-corelibs-foundation prior to Swift 5.1.
|
||||
#if canImport(Darwin) || swift(>=5.1) && !WASI // TEST_DISCOVERY_CONDITION
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -15,10 +19,6 @@ import OpenCombine
|
||||
import OpenCombineFoundation
|
||||
#endif
|
||||
|
||||
// PropertyListEncoder and PropertyListDecoder are unavailable in
|
||||
// swift-corelibs-foundation prior to Swift 5.1.
|
||||
#if canImport(Darwin) || swift(>=5.1) // TEST_DISCOVERY_CONDITION
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PropertyListEncoderTests: XCTestCase {
|
||||
|
||||
@@ -84,4 +84,4 @@ final class PropertyListEncoderTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
#endif // canImport(Darwin) || swift(>=5.1)
|
||||
#endif // canImport(Darwin) || swift(>=5.1) && !WASI
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user