Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46007658a1 | |||
| c4c7f2172d | |||
| 5b0a21a0b9 | |||
| f4e191b2ff | |||
| c275e51cdc | |||
| a84105133c | |||
| ef3ebd965a | |||
| a08b99c886 | |||
| 3398499540 | |||
| 1bf193ddaa | |||
| 3a88dfd76b | |||
| bd0b69d7cb | |||
| dba76c3c41 | |||
| 5863492753 | |||
| 2f2e16ee1f | |||
| bca131c2a4 | |||
| e999fafdce | |||
| b38830e0f1 | |||
| 525405f64d | |||
| 693d1145f8 | |||
| 2f9ddc2229 | |||
| bcd1b727f8 | |||
| 5d1034fcc0 | |||
| 2378f3d97e | |||
| 4a965830e7 | |||
| 9eabadb7c9 | |||
| dcfaec2c9d | |||
| 219ee38119 | |||
| 3a5389d398 | |||
| 69ead1c8fb | |||
| 8e6404592e | |||
| 14b7ced2fe | |||
| d7b9e87f6d | |||
| 4fd04b8a00 | |||
| 5f92ee05d2 | |||
| bdd703abb3 | |||
| e41c48a5cd | |||
| df0b8b08db | |||
| 7056143b99 | |||
| 0a965ba60a | |||
| 7dfaa4edea | |||
| 3e8f2774a4 | |||
| 68e9bbe164 | |||
| 0f71c33d72 | |||
| 3f61648f82 | |||
| c621ceb267 | |||
| 2aa297ec39 | |||
| 9cb27bb91b | |||
| d74f68da86 | |||
| f68dcd520f | |||
| 432fd4f48f | |||
| 9c6bbda0c4 | |||
| 3990ec2afb | |||
| 39dd9e40bf | |||
| fd7c0459b9 | |||
| f7145e7fa5 | |||
| ecd4766129 | |||
| e00a6f06fc | |||
| 23ee3a4b7b | |||
| 9c913124eb | |||
| 7ddd15b334 | |||
| 72753ef93c | |||
| 816426b48c | |||
| 47fb390081 | |||
| 1d3327f6bf | |||
| eb7478d430 | |||
| f69621f0e2 | |||
| 7f3cccf1ae | |||
| ec037dbb3d | |||
| 8a39f35d3f | |||
| 7fb92bffc6 | |||
| e441ea3048 | |||
| 22f7b6d10d | |||
| 7431d21c9c | |||
| 1d901fca7f | |||
| 9834eab0ea | |||
| 1ce9660ce9 | |||
| 313d6befa6 | |||
| 8c7f061892 | |||
| 2ac2470579 | |||
| 57c9ae8590 | |||
| d57c878651 | |||
| 7fa91778c2 | |||
| d15e604764 | |||
| 07c7a98d72 | |||
| 01ef05be1f | |||
| beee9d0d51 | |||
| aacd1a326c | |||
| 5528adcc67 | |||
| 1b810d0536 | |||
| 8b25238154 | |||
| 9b9915bde7 | |||
| 27f01e5f21 | |||
| 739eb47409 | |||
| 14d5a90e89 | |||
| 0e869bc861 | |||
| 2f38069166 | |||
| 97d07d0a14 | |||
| d3888a3808 | |||
| d2b8709afb | |||
| a28177e9c5 | |||
| cef19fce4b | |||
| 7f6bba62de | |||
| 26faf90356 | |||
| 511d676c30 | |||
| f6ecc28d25 | |||
| 03fe398395 | |||
| 47bbd8d81a | |||
| 5e3a18d8c7 | |||
| 74e1c1ae32 | |||
| 4dbc8cc09b | |||
| 2849b1c195 | |||
| c3e6905c68 | |||
| e75ad6b0de | |||
| c790e5f708 | |||
| 065b981934 | |||
| fbd1fd7014 |
@@ -0,0 +1,3 @@
|
||||
*.swift.gyb linguist-language=Swift
|
||||
|
||||
**/GENERATED-* linguist-generated=true
|
||||
+111
@@ -2,6 +2,7 @@
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
/.swiftpm
|
||||
|
||||
# Created by https://www.gitignore.io/api/Xcode
|
||||
# Edit at https://www.gitignore.io/?templates=Xcode
|
||||
@@ -42,3 +43,113 @@ DerivedData/
|
||||
# End of https://www.gitignore.io/api/Xcode
|
||||
|
||||
.idea
|
||||
|
||||
# Created by https://www.gitignore.io/api/Python
|
||||
# Edit at https://www.gitignore.io/?templates=Python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# End of https://www.gitignore.io/api/Python
|
||||
|
||||
+32
-6
@@ -72,12 +72,38 @@ line_length:
|
||||
generic_type_name:
|
||||
min_length: 3
|
||||
|
||||
file_types_order:
|
||||
order:
|
||||
- main_type
|
||||
- extension
|
||||
- supporting_type
|
||||
|
||||
attributes:
|
||||
always_on_line_above:
|
||||
- "@usableFromInline"
|
||||
|
||||
custom_rules:
|
||||
no_foundation_dependency:
|
||||
included: Sources/OpenCombine/
|
||||
name: "No Foundation Dependency"
|
||||
regex: "^import.*Foundation.*$"
|
||||
message: "We don't want to depend on Foundation"
|
||||
severity: error
|
||||
|
||||
no_dispatch_dependency:
|
||||
included: Sources/OpenCombine/
|
||||
name: "No Dispatch Dependency"
|
||||
regex: "^import.*Dispatch.*$"
|
||||
message: "We don't want to depend on Dispatch"
|
||||
severity: error
|
||||
|
||||
inheritance_colon:
|
||||
name: "Inheritance Colon"
|
||||
regex: '\s[A-Z_]\w*(<[\w\s:\.,]+>)?(?: +:\s*|:(?:\s{0}|\s{2,}))([\[|\(]*\S)'
|
||||
message: "Colons should be next to the identifier of the inheriting type"
|
||||
severity: warning
|
||||
match_kinds:
|
||||
- identifier
|
||||
- typeidentifier
|
||||
|
||||
dictionary_type_colon:
|
||||
name: "Dictionary Type Colon"
|
||||
regex: '\[\w+(?:(?:\s{0}|\s{2,}):| :(?:\s{0}|\s{2,})\w)'
|
||||
message: "Colon should be surrounded by a single whitespace in a dictionary literal"
|
||||
severity: warning
|
||||
match_kinds:
|
||||
- typeidentifier
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,102 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombine"
|
||||
BuildableName = "OpenCombine"
|
||||
BlueprintName = "OpenCombine"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombineTests"
|
||||
BuildableName = "OpenCombineTests"
|
||||
BlueprintName = "OpenCombineTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
enableThreadSanitizer = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombine"
|
||||
BuildableName = "OpenCombine"
|
||||
BlueprintName = "OpenCombine"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombineTests"
|
||||
BuildableName = "OpenCombineTests"
|
||||
BlueprintName = "OpenCombineTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OpenCombine"
|
||||
BuildableName = "OpenCombine"
|
||||
BlueprintName = "OpenCombine"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+49
-20
@@ -2,33 +2,43 @@ language: generic
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
taps:
|
||||
- danger/tap
|
||||
packages:
|
||||
- swiftlint
|
||||
- danger-swift
|
||||
update: true
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- .build
|
||||
- ~/.danger-swift
|
||||
- ~/.swiftenv
|
||||
- ~/Library/Caches/Homebrew
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- name: "Ubuntu 16.04 | Swift 5.1 | Tests"
|
||||
- name: "Ubuntu 16.04 | Swift 5.1.1 | Tests"
|
||||
os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
|
||||
env: SWIFT_VERSION="5.1.1" OPENCOMBINE_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | Tests"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES"
|
||||
# - name: "macOS 10.14 | Swift 5.1"
|
||||
# os: osx
|
||||
# osx_image: xcode10.2
|
||||
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
|
||||
# - name: "macOS 10.15 | Swift 5.1"
|
||||
# os: osx
|
||||
# osx_image: xcode11.0
|
||||
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_COMPATIBILITY_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | SwiftLint"
|
||||
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES" OPENCOMBINE_TEST="YES"
|
||||
- name: "iOS 13.1 | Swift 5.1.1 | Compatibility Tests"
|
||||
os: osx
|
||||
osx_image: xcode11.1
|
||||
env: SWIFT_VERSION="5.1.1" OPENCOMBINE_COMPATIBILITY_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | Code Quality"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: SWIFT_VERSION="5.0" SWIFT_LINT="YES"
|
||||
env: SWIFT_VERSION="5.0" RUN_DANGER="YES" SWIFT_LINT="YES"
|
||||
|
||||
before_cache:
|
||||
- brew cleanup
|
||||
|
||||
before_install:
|
||||
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
cat /proc/cpuinfo;
|
||||
@@ -41,21 +51,40 @@ install:
|
||||
gem install xcpretty;
|
||||
fi
|
||||
script:
|
||||
- if [[ $SWIFT_LINT != "YES" ]]; then
|
||||
swift test -c debug --enable-code-coverage --sanitize thread;
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-debug;
|
||||
else
|
||||
make test-debug;
|
||||
fi
|
||||
fi
|
||||
- if [[ $SWIFT_LINT != "YES" ]]; then
|
||||
swift test -c release;
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-debug-sanitize-thread;
|
||||
else
|
||||
make test-debug-sanitize-thread;
|
||||
fi
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
|
||||
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-release;
|
||||
else
|
||||
make test-release;
|
||||
fi
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
|
||||
swift test -c release -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST;
|
||||
make generate-compatibility-xcodeproj;
|
||||
set -o pipefail && xcodebuild -scheme OpenCombine-Package -sdk iphonesimulator13.1 -destination "platform=iOS Simulator,name=iPhone 11,OS=13.1" build test | xcpretty;
|
||||
fi
|
||||
- if [[ $SWIFT_LINT == "YES" ]]; then
|
||||
swiftlint lint --strict --reporter "emoji";
|
||||
fi
|
||||
- if [[ $RUN_DANGER == "YES" ]]; then
|
||||
danger-swift ci;
|
||||
fi
|
||||
after_success:
|
||||
- if [[ $CODE_COVERAGE == "YES" ]]; then
|
||||
swift package generate-xcodeproj --enable-code-coverage;
|
||||
make generate-xcodeproj;
|
||||
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
|
||||
bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN;
|
||||
bash <(curl -s https://codecov.io/bash);
|
||||
fi
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
@@ -0,0 +1,76 @@
|
||||
import Danger
|
||||
import Foundation
|
||||
|
||||
extension StringProtocol {
|
||||
func dropSuffix<S: StringProtocol>(_ suffix: S) -> SubSequence {
|
||||
if hasSuffix(suffix) {
|
||||
return self[..<index(endIndex, offsetBy: -suffix.count)]
|
||||
} else {
|
||||
return self[...]
|
||||
}
|
||||
}
|
||||
|
||||
func directoryAndFileName() -> (SubSequence, SubSequence) {
|
||||
let lastPathSeparator = lastIndex(of: "/")
|
||||
if let lastPathSeparator = lastPathSeparator {
|
||||
return (self[..<lastPathSeparator], self[index(after: lastPathSeparator)...])
|
||||
} else {
|
||||
return (".", self[...])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let danger = Danger()
|
||||
|
||||
let allCreatedAndModified = danger.git.createdFiles + danger.git.modifiedFiles
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a GYB template but forgot to run `make gyb`.
|
||||
|
||||
let modifiedTemplates = allCreatedAndModified.filter { $0.hasSuffix(".gyb") }
|
||||
|
||||
for modifiedTemplate in modifiedTemplates {
|
||||
let (directory, filename) = modifiedTemplate.directoryAndFileName()
|
||||
let generated = "\(directory)/GENERATED-\(filename.dropSuffix(".gyb"))"
|
||||
|
||||
if !allCreatedAndModified.contains(generated) {
|
||||
fail("""
|
||||
A template \(modifiedTemplate) was modified, but the file \(generated) \
|
||||
was not regenerated.
|
||||
|
||||
Run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
// Fail if the committer modified a generated file.
|
||||
// A template should be modified instead.
|
||||
|
||||
for modifiedGeneratedFile in danger.git.modifiedFiles
|
||||
where modifiedGeneratedFile.contains("GENERATED-")
|
||||
{
|
||||
let template = modifiedGeneratedFile
|
||||
.replacingOccurrences(of: "GENERATED-", with: "") + ".gyb"
|
||||
|
||||
if !danger.git.modifiedFiles.contains(template) {
|
||||
fail("""
|
||||
A generated file \(modifiedGeneratedFile) was modified, but \
|
||||
the template it was generated from was not modified.
|
||||
|
||||
Please modify the template \(template) instead, \
|
||||
run `make gyb` from the root of the project and commit the changes.
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwiftLint.lint(inline: true,
|
||||
configFile: ".swiftlint.yml",
|
||||
strict: true,
|
||||
lintAllFiles: true)
|
||||
|
||||
if danger.warnings.isEmpty, danger.fails.isEmpty {
|
||||
markdown("LGTM")
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
SWIFT_TEST_FLAGS=
|
||||
|
||||
debug:
|
||||
swift build -c debug
|
||||
|
||||
release:
|
||||
swift build -c release
|
||||
|
||||
test-debug:
|
||||
swift test -c debug $(SWIFT_TEST_FLAGS)
|
||||
|
||||
test-debug-sanitize-thread:
|
||||
swift test -c debug --sanitize thread $(SWIFT_TEST_FLAGS)
|
||||
|
||||
test-release:
|
||||
swift test -c release $(SWIFT_TEST_FLAGS)
|
||||
|
||||
swift-version:
|
||||
swift -version
|
||||
|
||||
test-compatibility:
|
||||
swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
|
||||
generate-compatibility-xcodeproj:
|
||||
swift package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
|
||||
open OpenCombine.xcodeproj
|
||||
|
||||
generate-xcodeproj:
|
||||
swift package generate-xcodeproj --enable-code-coverage
|
||||
|
||||
gyb:
|
||||
$(shell ./utils/recursively_gyb.sh)
|
||||
|
||||
clean:
|
||||
rm -rf .build
|
||||
|
||||
.PHONY: debug release \
|
||||
test-debug \
|
||||
test-release \
|
||||
swift-version \
|
||||
test-compatibility-debug \
|
||||
generate-compatibility-xcodeproj \
|
||||
generate-xcodeproj \
|
||||
gyb \
|
||||
clean
|
||||
+10
-3
@@ -6,13 +6,20 @@ let package = Package(
|
||||
name: "OpenCombine",
|
||||
products: [
|
||||
.library(name: "OpenCombine", targets: ["OpenCombine"]),
|
||||
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/broadwaylamb/GottaGoFast.git", from: "0.1.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "OpenCombine"),
|
||||
.target(name: "COpenCombineHelpers"),
|
||||
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
|
||||
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
|
||||
.testTarget(name: "OpenCombineTests",
|
||||
dependencies: ["OpenCombine", "GottaGoFast"])
|
||||
]
|
||||
dependencies: ["OpenCombine",
|
||||
"OpenCombineDispatch",
|
||||
"GottaGoFast"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])])
|
||||
],
|
||||
cxxLanguageStandard: .cxx1z
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[](https://codecov.io/gh/broadwaylamb/OpenCombine)
|
||||

|
||||

|
||||
[<img src="https://img.shields.io/badge/slack-OpenCombine-yellow.svg?logo=slack">](https://join.slack.com/t/opencombine/shared_invite/enQtNzE2MjE5NzkxODI0LTYxMjkzNDUxZWViZWI1Njc2YjBhODgxNjRjOTdkZTcxOGU2ZjJjZjYxMGI3NWZkN2RkNGFmZTUzNmU3MGE2ZWM)
|
||||
|
||||
Open-source implementation of Apple's [Combine](https://developer.apple.com/documentation/combine) framework for processing values over time.
|
||||
|
||||
@@ -21,9 +22,30 @@ You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff
|
||||
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
|
||||
|
||||
```
|
||||
$ swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
$ make test-compatibility
|
||||
```
|
||||
|
||||
Or enable the `-DOPENCOMBINE_COMPATIBILITY_TEST` compiler flag in Xcode's build settings. Note that on iOS only the latter will work.
|
||||
|
||||
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/broadwaylamb/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
|
||||
|
||||
#### GYB
|
||||
|
||||
Some publishers in OpenCombine (like `Publishers.MapKeyPath`, `Publishers.Merge`) exist in several
|
||||
different flavors in order to support several arities. For example, there are also `Publishers.MapKeyPath2`
|
||||
and `Publishers.MapKeyPath3`, which are very similar but different enough that Swift's type system
|
||||
can't help us here (because there's no support for variadic generics). Maintaining multiple instances of
|
||||
those generic types is tedious and error-prone (they can get out of sync), so we use the GYB tool for
|
||||
generating those instances from a template.
|
||||
|
||||
GYB is a Python script that evaluates Python code written inside a template file, so it's very flexible —
|
||||
templates can be arbitrarily complex. There is a good article about GYB on
|
||||
[NSHipster](https://nshipster.com/swift-gyb/).
|
||||
|
||||
GYB is part of the [Swift Open Source Project](https://github.com/apple/swift/blob/master/utils/gyb.py)
|
||||
and can be distributed under the same license as Swift itself.
|
||||
|
||||
GYB template files have the `.gyb` extension. Run `make gyb` to generate Swift code from those
|
||||
templates. The generated files are prefixed with `GENERATED-` and are checked into source control. Those
|
||||
files should never be edited directly. Instead, the `.gyb` template should be edited, and after that the files
|
||||
should be regenerated using `make gyb`.
|
||||
|
||||
+216
-1479
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// COpenCombineHelpers.cpp
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23/09/2019.
|
||||
//
|
||||
|
||||
#include "COpenCombineHelpers.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <os/lock.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
#define OPENCOMBINE_HANDLE_EXCEPTION_BEGIN try {
|
||||
|
||||
#define OPENCOMBINE_HANDLE_EXCEPTION_END } catch (...) { abort(); }
|
||||
|
||||
namespace {
|
||||
|
||||
static std::atomic<uint64_t> next_combine_identifier;
|
||||
|
||||
class PlatformIndependentMutex {
|
||||
public:
|
||||
virtual void lock() = 0;
|
||||
virtual void unlock() = 0;
|
||||
|
||||
virtual ~PlatformIndependentMutex() {}
|
||||
};
|
||||
|
||||
template <typename Mutex>
|
||||
class GenericMutex final : PlatformIndependentMutex {
|
||||
Mutex mutex_;
|
||||
public:
|
||||
void lock() override {
|
||||
mutex_.lock();
|
||||
}
|
||||
|
||||
void unlock() override {
|
||||
mutex_.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __APPLE__
|
||||
bool isOSUnfairLockAvailable() {
|
||||
// We're linking weakly, so if we're back-deploying, this will be null.
|
||||
return os_unfair_lock_lock != nullptr;
|
||||
}
|
||||
|
||||
template <>
|
||||
class GenericMutex<os_unfair_lock> final : PlatformIndependentMutex {
|
||||
os_unfair_lock mutex_ = OS_UNFAIR_LOCK_INIT;
|
||||
public:
|
||||
GenericMutex() = default;
|
||||
GenericMutex(const GenericMutex&) = delete;
|
||||
GenericMutex& operator=(const GenericMutex&) = delete;
|
||||
|
||||
void lock() override {
|
||||
os_unfair_lock_lock(&mutex_);
|
||||
}
|
||||
|
||||
void unlock() override {
|
||||
os_unfair_lock_unlock(&mutex_);
|
||||
}
|
||||
};
|
||||
#endif // __APPLE__
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
uint64_t opencombine_next_combine_identifier(void) {
|
||||
return next_combine_identifier.fetch_add(1);
|
||||
}
|
||||
|
||||
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (isOSUnfairLockAvailable()) {
|
||||
return {new GenericMutex<os_unfair_lock>};
|
||||
} else {
|
||||
return {new GenericMutex<std::mutex>};
|
||||
}
|
||||
#else
|
||||
return {new GenericMutex<std::mutex>};
|
||||
#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 GenericMutex<std::recursive_mutex>};
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_lock(OpenCombineUnfairLock lock) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock mutex) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(mutex.opaque)->unlock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock lock) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock mutex) {
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
|
||||
static_cast<PlatformIndependentMutex*>(mutex.opaque)->unlock();
|
||||
OPENCOMBINE_HANDLE_EXCEPTION_END
|
||||
}
|
||||
|
||||
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock) {
|
||||
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
|
||||
}
|
||||
|
||||
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock) {
|
||||
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// COpenCombineHelpers.h
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 23/09/2019.
|
||||
//
|
||||
|
||||
#ifndef COPENCOMBINEHELPERS_H
|
||||
#define COPENCOMBINEHELPERS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __has_attribute(swift_name)
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
|
||||
#else
|
||||
# define OPENCOMBINE_SWIFT_NAME(_name)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma mark - CombineIdentifier
|
||||
|
||||
uint64_t opencombine_next_combine_identifier(void)
|
||||
OPENCOMBINE_SWIFT_NAME(nextCombineIdentifier());
|
||||
|
||||
#pragma mark - OpenCombineUnfairLock
|
||||
|
||||
/// A wrapper around an opaque pointer for type safety in Swift.
|
||||
typedef struct OpenCombineUnfairLock {
|
||||
void* _Nonnull opaque;
|
||||
} OPENCOMBINE_SWIFT_NAME(UnfairLock) OpenCombineUnfairLock;
|
||||
|
||||
/// Allocates a lock object. The allocated object must be destroyed by calling
|
||||
/// the destroy() method.
|
||||
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairLock.allocate());
|
||||
|
||||
void opencombine_unfair_lock_lock(OpenCombineUnfairLock)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairLock.lock(self:));
|
||||
|
||||
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairLock.unlock(self:));
|
||||
|
||||
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairLock.deallocate(self:));
|
||||
|
||||
#pragma mark - OpenCombineUnfairRecursiveLock
|
||||
|
||||
/// A wrapper around an opaque pointer for type safety in Swift.
|
||||
typedef struct OpenCombineUnfairRecursiveLock {
|
||||
void* _Nonnull opaque;
|
||||
} OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock) OpenCombineUnfairRecursiveLock;
|
||||
|
||||
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.allocate());
|
||||
|
||||
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.lock(self:));
|
||||
|
||||
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.unlock(self:));
|
||||
|
||||
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock)
|
||||
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.deallocate(self:));
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* COPENCOMBINEHELPERS_H */
|
||||
@@ -10,6 +10,7 @@
|
||||
/// Subscriber implementations can use this type to provide a “cancellation token” that
|
||||
/// makes it possible for a caller to cancel a publisher, but not to use the
|
||||
/// `Subscription` object to request items.
|
||||
/// An AnyCancellable instance automatically calls `cancel()` when deinitialized.
|
||||
public final class AnyCancellable: Cancellable, Hashable {
|
||||
|
||||
private var _cancel: (() -> Void)?
|
||||
@@ -21,7 +22,7 @@ public final class AnyCancellable: Cancellable, Hashable {
|
||||
_cancel = cancel
|
||||
}
|
||||
|
||||
public init<CancellableType: Cancellable>(_ canceller: CancellableType) {
|
||||
public init<OtherCancellable: Cancellable>(_ canceller: OtherCancellable) {
|
||||
_cancel = canceller.cancel
|
||||
}
|
||||
|
||||
@@ -39,7 +40,7 @@ public final class AnyCancellable: Cancellable, Hashable {
|
||||
}
|
||||
|
||||
deinit {
|
||||
cancel()
|
||||
_cancel?()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,25 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Wraps this publisher with a type eraser.
|
||||
///
|
||||
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher` to
|
||||
/// the downstream subscriber, rather than this publisher’s actual type.
|
||||
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing publisher.
|
||||
///
|
||||
/// Use `AnyPublisher` to wrap a publisher whose type has details you don’t want to expose
|
||||
/// to subscribers or other publishers.
|
||||
public struct AnyPublisher<Output, Failure: Error> {
|
||||
|
||||
public struct AnyPublisher<Output, Failure: Error>
|
||||
: CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
@usableFromInline
|
||||
internal let box: PublisherBoxBase<Output, Failure>
|
||||
|
||||
@@ -24,6 +37,14 @@ public struct AnyPublisher<Output, Failure: Error> {
|
||||
{
|
||||
box = PublisherBox(base: publisher)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "AnyPublisher"
|
||||
}
|
||||
|
||||
public var playgroundDescription: Any {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyPublisher: Publisher {
|
||||
@@ -36,10 +57,10 @@ extension AnyPublisher: Publisher {
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
@inlinable
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
box.receive(subscriber: subscriber)
|
||||
box.subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,19 +72,18 @@ internal class PublisherBoxBase<Output, Failure: Error>: Publisher {
|
||||
@inlinable
|
||||
internal init() {}
|
||||
|
||||
@inlinable
|
||||
internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
@usableFromInline
|
||||
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal final class PublisherBox<PublisherType: Publisher>
|
||||
: PublisherBoxBase<PublisherType.Output,
|
||||
PublisherType.Failure> {
|
||||
|
||||
: PublisherBoxBase<PublisherType.Output, PublisherType.Failure>
|
||||
{
|
||||
@usableFromInline
|
||||
internal let base: PublisherType
|
||||
|
||||
@@ -74,9 +94,9 @@ internal final class PublisherBox<PublisherType: Publisher>
|
||||
}
|
||||
|
||||
@inlinable
|
||||
override internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
override internal func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
base.receive(subscriber: subscriber)
|
||||
base.subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
//
|
||||
// AnySubject.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
public final class AnySubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _box: SubjectBoxBase<Output, Failure>
|
||||
|
||||
public init<SubjectType: Subject>(_ subject: SubjectType)
|
||||
where Output == SubjectType.Output, Failure == SubjectType.Failure
|
||||
{
|
||||
_box = SubjectBox(base: subject)
|
||||
}
|
||||
|
||||
public init(
|
||||
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
|
||||
_ send: @escaping (Output) -> Void,
|
||||
_ sendCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
|
||||
) {
|
||||
_box = ClosureBasedSubject(subscribe, send, sendCompletion)
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
_box.receive(subscriber: subscriber)
|
||||
}
|
||||
|
||||
public func send(_ value: Output) {
|
||||
_box.send(value)
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
_box.send(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing base class. Its concrete subclass is generic over the underlying
|
||||
/// publisher.
|
||||
private class SubjectBoxBase<Output, Failure: Error>: Subject {
|
||||
|
||||
func send(_ value: Output) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<Failure>) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
private final class SubjectBox<SubjectType: Subject>
|
||||
: SubjectBoxBase<SubjectType.Output,
|
||||
SubjectType.Failure> {
|
||||
|
||||
private let base: SubjectType
|
||||
|
||||
init(base: SubjectType) {
|
||||
self.base = base
|
||||
}
|
||||
|
||||
override func send(_ value: Output) {
|
||||
base.send(value)
|
||||
}
|
||||
|
||||
override func send(completion: Subscribers.Completion<Failure>) {
|
||||
base.send(completion: completion)
|
||||
}
|
||||
|
||||
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
base.receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ClosureBasedSubject<Output, Failure: Error>
|
||||
: SubjectBoxBase<Output, Failure>
|
||||
{
|
||||
|
||||
private let _subscribe: (AnySubscriber<Output, Failure>) -> Void
|
||||
private let _receive: (Output) -> Void
|
||||
private let _receiveCompletion: (Subscribers.Completion<Failure>) -> Void
|
||||
|
||||
init(
|
||||
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
|
||||
_ receive: @escaping (Output) -> Void,
|
||||
_ receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
|
||||
) {
|
||||
_subscribe = subscribe
|
||||
_receive = receive
|
||||
_receiveCompletion = receiveCompletion
|
||||
}
|
||||
|
||||
override func send(_ value: Output) {
|
||||
_receive(value)
|
||||
}
|
||||
|
||||
override func send(completion: Subscribers.Completion<Failure>) {
|
||||
_receiveCompletion(completion)
|
||||
}
|
||||
|
||||
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
_subscribe(AnySubscriber(subscriber))
|
||||
}
|
||||
}
|
||||
@@ -15,32 +15,65 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private let _box: SubscriberBoxBase<Input, Failure>
|
||||
@usableFromInline
|
||||
internal let box: AnySubscriberBase<Input, Failure>
|
||||
|
||||
@usableFromInline
|
||||
internal let descriptionThunk: () -> String
|
||||
|
||||
@usableFromInline
|
||||
internal let customMirrorThunk: () -> Mirror
|
||||
|
||||
@usableFromInline
|
||||
internal let playgroundDescriptionThunk: () -> Any
|
||||
|
||||
public let combineIdentifier: CombineIdentifier
|
||||
|
||||
public var description: String { return _box.description }
|
||||
public var description: String { return descriptionThunk() }
|
||||
|
||||
public var customMirror: Mirror { return _box.customMirror }
|
||||
public var customMirror: Mirror { return customMirrorThunk() }
|
||||
|
||||
/// A custom playground description for this instance.
|
||||
public var playgroundDescription: Any { return description }
|
||||
public var playgroundDescription: Any { return playgroundDescriptionThunk() }
|
||||
|
||||
/// Creates a type-erasing subscriber to wrap an existing subscriber.
|
||||
///
|
||||
/// - Parameter s: The subscriber to type-erase.
|
||||
public init<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
|
||||
where Input == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Input == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
_box = SubscriberBox(base: subscriber)
|
||||
combineIdentifier = subscriber.combineIdentifier
|
||||
|
||||
box = AnySubscriberBox(subscriber)
|
||||
|
||||
if let description = subscriber as? CustomStringConvertible {
|
||||
descriptionThunk = { description.description }
|
||||
} else {
|
||||
let fixedDescription = String(describing: type(of: subscriber))
|
||||
descriptionThunk = { fixedDescription }
|
||||
}
|
||||
|
||||
customMirrorThunk = {
|
||||
(subscriber as? CustomReflectable)?.customMirror
|
||||
?? Mirror(subscriber, children: EmptyCollection())
|
||||
}
|
||||
|
||||
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
|
||||
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
|
||||
} else if let desccription = subscriber as? CustomStringConvertible {
|
||||
playgroundDescriptionThunk = { desccription.description }
|
||||
} else {
|
||||
let fixedDescription = String(describing: type(of: subscriber))
|
||||
playgroundDescriptionThunk = { fixedDescription }
|
||||
}
|
||||
}
|
||||
|
||||
public init<SubjectType: Subject>(_ subject: SubjectType)
|
||||
where Input == SubjectType.Output, Failure == SubjectType.Failure
|
||||
public init<Subject: OpenCombine.Subject>(_ subject: Subject)
|
||||
where Input == Subject.Output, Failure == Subject.Failure
|
||||
{
|
||||
_box = SubjectSubscriber(subject)
|
||||
combineIdentifier = CombineIdentifier(_box)
|
||||
self.init(SubjectSubscriber(subject))
|
||||
}
|
||||
|
||||
/// Creates a type-erasing subscriber that executes the provided closures.
|
||||
@@ -52,140 +85,140 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
/// the publisher.
|
||||
/// - receiveCompletion: A closure to execute when the subscriber receives
|
||||
/// a completion callback from the publisher.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init(receiveSubscription: ((Subscription) -> Void)? = nil,
|
||||
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
|
||||
_box = ClosureBasedSubscriber(receiveSubscription: receiveSubscription,
|
||||
receiveValue: receiveValue,
|
||||
receiveCompletion: receiveCompletion)
|
||||
|
||||
box = ClosureBasedAnySubscriber(
|
||||
receiveSubscription ?? { _ in },
|
||||
receiveValue ?? { _ in .none },
|
||||
receiveCompletion ?? { _ in }
|
||||
)
|
||||
|
||||
combineIdentifier = CombineIdentifier()
|
||||
descriptionThunk = { "Anonymous AnySubscriber" }
|
||||
customMirrorThunk = { Mirror(reflecting: "Anonymous AnySubscriber") }
|
||||
playgroundDescriptionThunk = { "Anonymous AnySubscriber" }
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public func receive(subscription: Subscription) {
|
||||
_box.receive(subscription: subscription)
|
||||
box.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public func receive(_ value: Input) -> Subscribers.Demand {
|
||||
return _box.receive(value)
|
||||
return box.receive(value)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public func receive(completion: Subscribers.Completion<Failure>) {
|
||||
_box.receive(completion: completion)
|
||||
box.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing base class. Its concrete subclass is generic over the underlying
|
||||
/// publisher.
|
||||
internal class SubscriberBoxBase<Input, Failure: Error>: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable {
|
||||
/// subscriber.
|
||||
@usableFromInline
|
||||
internal class AnySubscriberBase<Input, Failure: Error>: Subscriber {
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
internal init() {}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
deinit {}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(subscription: Subscription) {
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
fatalError()
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
internal var description: String { return "AnySubscriber" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
return Mirror(combineIdentifier, children: EmptyCollection())
|
||||
abstractMethod()
|
||||
}
|
||||
}
|
||||
|
||||
internal final class SubscriberBox<SubscriberType: Subscriber>
|
||||
: SubscriberBoxBase<SubscriberType.Input, SubscriberType.Failure>
|
||||
@usableFromInline
|
||||
internal final class AnySubscriberBox<Base: Subscriber>
|
||||
: AnySubscriberBase<Base.Input, Base.Failure>
|
||||
{
|
||||
@usableFromInline
|
||||
internal let base: Base
|
||||
|
||||
private let base: SubscriberType
|
||||
|
||||
internal init(base: SubscriberType) {
|
||||
@inlinable
|
||||
internal init(_ base: Base) {
|
||||
self.base = base
|
||||
}
|
||||
|
||||
@inlinable
|
||||
deinit {}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(subscription: Subscription) {
|
||||
base.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
override internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
@inlinable
|
||||
override internal func receive(_ input: Base.Input) -> Subscribers.Demand {
|
||||
return base.receive(input)
|
||||
}
|
||||
|
||||
override internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
@inlinable
|
||||
override internal func receive(completion: Subscribers.Completion<Base.Failure>) {
|
||||
base.receive(completion: completion)
|
||||
}
|
||||
|
||||
override internal var customMirror: Mirror { return Mirror(reflecting: base) }
|
||||
|
||||
override internal var description: String { return String(describing: base) }
|
||||
}
|
||||
|
||||
internal final class ClosureBasedSubscriber<Input, Failure: Error>
|
||||
: SubscriberBoxBase<Input, Failure>
|
||||
@usableFromInline
|
||||
internal final class ClosureBasedAnySubscriber<Input, Failure: Error>
|
||||
: AnySubscriberBase<Input, Failure>
|
||||
{
|
||||
@usableFromInline
|
||||
internal let receiveSubscriptionThunk: (Subscription) -> Void
|
||||
|
||||
private let _receiveSubscription: ((Subscription) -> Void)?
|
||||
private let _receiveValue: ((Input) -> Subscribers.Demand)?
|
||||
private let _receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)?
|
||||
@usableFromInline
|
||||
internal let receiveValueThunk: (Input) -> Subscribers.Demand
|
||||
|
||||
internal init(receiveSubscription: ((Subscription) -> Void)? = nil,
|
||||
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
|
||||
_receiveSubscription = receiveSubscription
|
||||
_receiveValue = receiveValue
|
||||
_receiveCompletion = receiveCompletion
|
||||
@usableFromInline
|
||||
internal let receiveCompletionThunk: (Subscribers.Completion<Failure>) -> Void
|
||||
|
||||
@inlinable
|
||||
internal init(_ rcvSubscription: @escaping (Subscription) -> Void,
|
||||
_ rcvValue: @escaping (Input) -> Subscribers.Demand,
|
||||
_ rcvCompletion: @escaping (Subscribers.Completion<Failure>) -> Void) {
|
||||
receiveSubscriptionThunk = rcvSubscription
|
||||
receiveValueThunk = rcvValue
|
||||
receiveCompletionThunk = rcvCompletion
|
||||
}
|
||||
|
||||
@inlinable
|
||||
deinit {}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(subscription: Subscription) {
|
||||
_receiveSubscription?(subscription)
|
||||
receiveSubscriptionThunk(subscription)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return _receiveValue?(input) ?? .none
|
||||
return receiveValueThunk(input)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
_receiveCompletion?(completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal final class SubjectSubscriber<SubjectType: Subject>
|
||||
: SubscriberBoxBase<SubjectType.Output, SubjectType.Failure>
|
||||
{
|
||||
internal var parent: SubjectType?
|
||||
internal var upstreamSubscription: Subscription?
|
||||
|
||||
internal init(_ parent: SubjectType) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
override internal func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
override internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
parent?.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
override internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
parent?.send(completion: completion)
|
||||
}
|
||||
|
||||
override internal var description: String { return "Subject" }
|
||||
|
||||
override internal var customMirror: Mirror {
|
||||
let children: [(label: String?, value: Any)] = [
|
||||
(label: "parent", value: parent as Any),
|
||||
(label: "upstreamSubscription", value: upstreamSubscription as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
receiveCompletionThunk(completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// Cancelable.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol indicating that an activity or action may be canceled.
|
||||
///
|
||||
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
|
||||
/// as timers, network access, or disk I/O.
|
||||
public protocol Cancellable {
|
||||
|
||||
/// Cancel the activity.
|
||||
func cancel()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Cancellable.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol indicating that an activity or action may be canceled.
|
||||
///
|
||||
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
|
||||
/// as timers, network access, or disk I/O.
|
||||
public protocol Cancellable {
|
||||
|
||||
/// Cancel the activity.
|
||||
func cancel()
|
||||
}
|
||||
|
||||
extension Cancellable {
|
||||
|
||||
/// Stores this Cancellable in the specified collection.
|
||||
/// Parameters:
|
||||
/// - collection: The collection to store this Cancellable.
|
||||
public func store<Cancellables: RangeReplaceableCollection>(
|
||||
in collection: inout Cancellables
|
||||
) where Cancellables.Element == AnyCancellable {
|
||||
AnyCancellable(self).store(in: &collection)
|
||||
}
|
||||
|
||||
/// Stores this Cancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - collection: The set to store this Cancellable.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
AnyCancellable(self).store(in: &set)
|
||||
}
|
||||
}
|
||||
@@ -5,35 +5,21 @@
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
import func COpenCombineHelpers.nextCombineIdentifier
|
||||
|
||||
public struct CombineIdentifier: Hashable, CustomStringConvertible {
|
||||
|
||||
@usableFromInline
|
||||
internal static var _counter: UInt = 0
|
||||
private let id: UInt64
|
||||
|
||||
@usableFromInline
|
||||
internal static var _counterLock = Lock(recursive: false)
|
||||
|
||||
@usableFromInline
|
||||
internal let _id: UInt
|
||||
|
||||
@inlinable
|
||||
public init() {
|
||||
|
||||
var id: UInt = 0
|
||||
|
||||
CombineIdentifier._counterLock.do {
|
||||
id = CombineIdentifier._counter
|
||||
CombineIdentifier._counter += 1
|
||||
}
|
||||
|
||||
_id = id
|
||||
self.id = nextCombineIdentifier()
|
||||
}
|
||||
|
||||
public init(_ obj: AnyObject) {
|
||||
_id = UInt(bitPattern: ObjectIdentifier(obj))
|
||||
id = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "0x\(String(_id, radix: 16))"
|
||||
return "0x\(String(id, radix: 16))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,21 +5,32 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
/// A subject that wraps a single value and publishes a new element whenever the value
|
||||
/// changes.
|
||||
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _lock = Lock(recursive: true)
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
private var _value: Output
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
internal var hasAnyDownstreamDemand = false
|
||||
|
||||
/// The value wrapped by this subject, published as a new element whenever it changes.
|
||||
public var value: Output {
|
||||
didSet {
|
||||
send(value)
|
||||
get {
|
||||
return _value
|
||||
}
|
||||
set {
|
||||
send(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,23 +38,45 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
///
|
||||
/// - Parameter value: The initial value to publish.
|
||||
public init(_ value: Output) {
|
||||
self.value = value
|
||||
self._value = value
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
|
||||
|
||||
_lock.do {
|
||||
_subscriptions.append(subscription)
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
subscriber.receive(subscription: subscription)
|
||||
public func send(subscription: Subscription) {
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Output == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
_lock.do {
|
||||
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
_lock.do {
|
||||
_value = input
|
||||
for subscription in _subscriptions where !subscription.isCompleted {
|
||||
if subscription._demand > 0 {
|
||||
subscription._offer(input)
|
||||
@@ -102,7 +135,7 @@ extension CurrentValueSubject {
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard demand > 0 else { return }
|
||||
precondition(demand > 0)
|
||||
_parent?._lock.do {
|
||||
if !_delivered, let value = _parent?.value {
|
||||
_offer(value)
|
||||
@@ -111,6 +144,7 @@ extension CurrentValueSubject {
|
||||
} else {
|
||||
_demand = demand
|
||||
}
|
||||
_parent?.hasAnyDownstreamDemand = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,3 +153,7 @@ extension CurrentValueSubject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentValueSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "CurrentValueSubject" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Locking.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension UnfairLock {
|
||||
|
||||
@inlinable
|
||||
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
|
||||
lock()
|
||||
defer { unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
|
||||
extension UnfairRecursiveLock {
|
||||
|
||||
@inlinable
|
||||
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
|
||||
lock()
|
||||
defer { unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// PartialCompletion.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 22.09.2019.
|
||||
//
|
||||
|
||||
/// A value of this type is returned by the overridden `receive(newValue:)` method
|
||||
/// of the `ReduceProducer` and `FilterProducer` classes.
|
||||
internal enum PartialCompletion<Value, Failure: Error> {
|
||||
|
||||
/// Indicate that we should continue accepting the upstream's output.
|
||||
case `continue`(Value)
|
||||
|
||||
/// Indicate that no values should be received from the upstream anymore.
|
||||
case finished
|
||||
|
||||
/// Indicate that there was a failure and we should send it downstream.
|
||||
case failure(Failure)
|
||||
}
|
||||
|
||||
extension PartialCompletion where Value == Void {
|
||||
|
||||
/// Indicate that we should continue accepting the upstream's output.
|
||||
internal static var `continue`: PartialCompletion { return .continue(()) }
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
//
|
||||
// ReduceProducer.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 22.09.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
/// A helper class that acts like both subscriber and subscription.
|
||||
///
|
||||
/// Reduce-like operators send an instance of their `Inner` class that is subclass
|
||||
/// of this class to the upstream publisher (as subscriber) and
|
||||
/// to the downstream subcriber (as subsription).
|
||||
///
|
||||
/// Reduce-like operators include `Publishers.Reduce`, `Publishers.TryReduce`,
|
||||
/// `Publishers.Count`, `Publishers.FirstWhere`, `Publishers.AllSatisfy` and more.
|
||||
///
|
||||
/// Subclasses must override the `receive(newValue:)` and `description`.
|
||||
internal class ReduceProducer<Downstream: Subscriber,
|
||||
Input,
|
||||
Output,
|
||||
UpstreamFailure: Error,
|
||||
Reducer>
|
||||
: CustomStringConvertible,
|
||||
CustomReflectable
|
||||
where Downstream.Input == Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
// MARK: - State
|
||||
|
||||
internal final var result: Output?
|
||||
|
||||
private let initial: Output?
|
||||
|
||||
internal final let reduce: Reducer
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var downstreamRequested = false
|
||||
|
||||
private var cancelled = false
|
||||
|
||||
private var completed = false
|
||||
|
||||
private var upstreamCompleted = false
|
||||
|
||||
private var empty = true
|
||||
|
||||
internal init(downstream: Downstream, initial: Output?, reduce: Reducer) {
|
||||
self.downstream = downstream
|
||||
self.initial = initial
|
||||
self.result = initial
|
||||
self.reduce = reduce
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
// MARK: - Abstract methods
|
||||
|
||||
internal func receive(
|
||||
newValue: Input
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
internal var description: String {
|
||||
abstractMethod()
|
||||
}
|
||||
|
||||
// MARK: - CustomReflectable
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("result", result as Any),
|
||||
("initial", initial as Any),
|
||||
("status", status)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// - Precondition: `lock` is held.
|
||||
private func receiveFinished() {
|
||||
guard !cancelled, !completed, !upstreamCompleted else {
|
||||
lock.unlock()
|
||||
// This should never happen, because `receive(completion:)`
|
||||
// (from which this function is called) early exists if
|
||||
// `status` is `.terminal`.
|
||||
assertionFailure("The subscription should have been terminated by now")
|
||||
return
|
||||
}
|
||||
upstreamCompleted = true
|
||||
self.completed = downstreamRequested || empty
|
||||
let completed = self.completed
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
|
||||
if completed {
|
||||
sendResultAndFinish(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Precondition: `lock` is held.
|
||||
private func receiveFailure(_ failure: UpstreamFailure) {
|
||||
guard !cancelled, !completed, !upstreamCompleted else {
|
||||
lock.unlock()
|
||||
// This should never happen, because `receive(completion:)`
|
||||
// (from which this function is called) early exists if
|
||||
// `status` is `.terminal`.
|
||||
assertionFailure("The subscription should have been terminated by now")
|
||||
return
|
||||
}
|
||||
upstreamCompleted = true
|
||||
completed = true
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .failure(failure as! Downstream.Failure))
|
||||
}
|
||||
|
||||
private func sendResultAndFinish(_ result: Output?) {
|
||||
assert(completed && upstreamCompleted)
|
||||
if let result = result {
|
||||
_ = downstream.receive(result)
|
||||
}
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
}
|
||||
|
||||
extension ReduceProducer: Subscriber {
|
||||
internal func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
empty = false
|
||||
lock.unlock()
|
||||
|
||||
// Combine doesn't hold the lock when calling `receive(newValue:)`.
|
||||
//
|
||||
// This can lead to data races if the contract is violated
|
||||
// (like when we receive input from multiple threads simultaneously).
|
||||
switch self.receive(newValue: input) {
|
||||
case .continue:
|
||||
break
|
||||
case .finished:
|
||||
lock.lock()
|
||||
upstreamCompleted = true
|
||||
let downstreamRequested = self.downstreamRequested
|
||||
if downstreamRequested {
|
||||
completed = true
|
||||
}
|
||||
status = .terminal
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
|
||||
subscription.cancel()
|
||||
|
||||
guard downstreamRequested else { break }
|
||||
|
||||
sendResultAndFinish(result)
|
||||
case let .failure(error):
|
||||
lock.lock()
|
||||
upstreamCompleted = true
|
||||
completed = true
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<UpstreamFailure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
switch completion {
|
||||
case .finished:
|
||||
receiveFinished()
|
||||
case let .failure(error):
|
||||
receiveFailure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ReduceProducer: Subscription {
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard !downstreamRequested, !cancelled, !completed else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
downstreamRequested = true
|
||||
guard upstreamCompleted else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
completed = true
|
||||
let result = self.result
|
||||
lock.unlock()
|
||||
sendResultAndFinish(result)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension ReduceProducer: CustomPlaygroundDisplayConvertible {
|
||||
internal var playgroundDescription: Any { return description }
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// SubjectSubscriber.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16/09/2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
internal final class SubjectSubscriber<Downstream: Subject>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible,
|
||||
Subscription
|
||||
{
|
||||
private let lock = UnfairLock.allocate()
|
||||
private var downstreamSubject: Downstream?
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
private var isCancelled: Bool { return downstreamSubject == nil }
|
||||
|
||||
internal init(_ parent: Downstream) {
|
||||
self.downstreamSubject = parent
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard upstreamSubscription == nil, let subject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = subscription
|
||||
lock.unlock()
|
||||
subject.send(subscription: self)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard let downstreamSubject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
guard upstreamSubscription != nil else { APIViolationValueBeforeSubscription() }
|
||||
lock.unlock()
|
||||
downstreamSubject.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
lock.lock()
|
||||
guard let subject = downstreamSubject else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
guard upstreamSubscription != nil else { APIViolationUnexpectedCompletion() }
|
||||
lock.unlock()
|
||||
subject.send(completion: completion)
|
||||
downstreamSubject = nil
|
||||
}
|
||||
|
||||
internal var description: String { return "Subject" }
|
||||
|
||||
internal var playgroundDescription: Any { return description }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstreamSubject", downstreamSubject as Any),
|
||||
("upstreamSubscription", upstreamSubscription as Any),
|
||||
("subject", downstreamSubject as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard let subscription = upstreamSubscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
lock.lock()
|
||||
if isCancelled {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
guard let subscription = upstreamSubscription else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
upstreamSubscription = nil
|
||||
downstreamSubject = nil
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// SubscriptionStatus.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 21.09.2019.
|
||||
//
|
||||
|
||||
internal enum SubscriptionStatus {
|
||||
case awaitingSubscription
|
||||
case subscribed(Subscription)
|
||||
case terminal
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Violations.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16/09/2019.
|
||||
//
|
||||
|
||||
internal func APIViolationValueBeforeSubscription(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("""
|
||||
API Violation: received an unexpected value before receiving a Subscription
|
||||
""",
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
internal func APIViolationUnexpectedCompletion(file: StaticString = #file,
|
||||
line: UInt = #line) -> Never {
|
||||
fatalError("API Violation: received an unexpected completion", file: file, line: line)
|
||||
}
|
||||
|
||||
internal func abstractMethod(file: StaticString = #file, line: UInt = #line) -> Never {
|
||||
fatalError("Abstract method call", file: file, line: line)
|
||||
}
|
||||
|
||||
extension Subscribers.Demand {
|
||||
internal func assertNonZero(file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
if self == .none {
|
||||
fatalError("API Violation: demand must not be zero", file: file, line: line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,9 +139,7 @@ public struct ImmediateScheduler: Scheduler {
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
fatalError(
|
||||
"Attempt to schedule something in the future on the immediate scheduler"
|
||||
)
|
||||
action()
|
||||
}
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
@@ -151,8 +149,7 @@ public struct ImmediateScheduler: Scheduler {
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
fatalError(
|
||||
"Attempt to schedule something in the future on the immediate scheduler"
|
||||
)
|
||||
action()
|
||||
return Subscriptions.empty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// Locking.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
#if canImport(Darwin)
|
||||
import Darwin
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#else
|
||||
#error("How to do locking on this platform?")
|
||||
#endif
|
||||
|
||||
@usableFromInline
|
||||
internal final class Lock {
|
||||
|
||||
@usableFromInline
|
||||
internal var _mutex = pthread_mutex_t()
|
||||
|
||||
@inlinable
|
||||
internal init(recursive: Bool) {
|
||||
var attrib = pthread_mutexattr_t()
|
||||
pthread_mutexattr_init(&attrib)
|
||||
if recursive {
|
||||
pthread_mutexattr_settype(&attrib, Int32(PTHREAD_MUTEX_RECURSIVE))
|
||||
}
|
||||
pthread_mutex_init(&_mutex, &attrib)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
deinit {
|
||||
pthread_mutex_destroy(&_mutex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func _lock() {
|
||||
pthread_mutex_lock(&_mutex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func _unlock() {
|
||||
pthread_mutex_unlock(&_mutex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
|
||||
_lock()
|
||||
defer { _unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
@@ -5,37 +5,65 @@
|
||||
// Created by Sergej Jaskiewicz on 11.06.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
/// A subject that passes along values and completion.
|
||||
///
|
||||
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
|
||||
/// specific values on-demand during tests.
|
||||
public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _lock = Lock(recursive: true)
|
||||
private let _lock = UnfairRecursiveLock.allocate()
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
internal var hasAnyDownstreamDemand = false
|
||||
|
||||
public init() {}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
|
||||
|
||||
_lock.do {
|
||||
_subscriptions.append(subscription)
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
_lock.deallocate()
|
||||
}
|
||||
|
||||
subscriber.receive(subscription: subscription)
|
||||
public func send(subscription: Subscription) {
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
if hasAnyDownstreamDemand {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
_lock.do {
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
_lock.do {
|
||||
for subscription in _subscriptions
|
||||
where !subscription.isCompleted && subscription._demand > 0
|
||||
where !subscription._isCompleted && subscription._demand > 0
|
||||
{
|
||||
let newDemand = subscription._downstream?.receive(input) ?? .none
|
||||
subscription._demand += newDemand
|
||||
@@ -45,13 +73,23 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
_completion = completion
|
||||
_lock.do {
|
||||
_completion = completion
|
||||
for subscriber in _subscriptions {
|
||||
subscriber._receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func _acknowledgeDownstreamDemand() {
|
||||
_lock.do {
|
||||
guard !hasAnyDownstreamDemand else { return }
|
||||
hasAnyDownstreamDemand = true
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject {
|
||||
@@ -64,7 +102,7 @@ extension PassthroughSubject {
|
||||
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
|
||||
var isCompleted: Bool {
|
||||
fileprivate var _isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
|
||||
@@ -75,20 +113,26 @@ extension PassthroughSubject {
|
||||
}
|
||||
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
if !_isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
precondition(demand > 0, "demand must not be zero")
|
||||
_parent?._lock.do {
|
||||
_demand = demand
|
||||
_demand += demand
|
||||
}
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
fileprivate func cancel() {
|
||||
_parent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "PassthroughSubject" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Published.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Евгений Богомолов on 01/09/2019.
|
||||
//
|
||||
|
||||
#if swift(>=5.1)
|
||||
/// Adds a `Publisher` to a property.
|
||||
///
|
||||
/// Properties annotated with `@Published` contain both the stored value
|
||||
/// and a publisher which sends any new values after the property value
|
||||
/// has been sent. New subscribers will receive the current value
|
||||
/// of the property first.
|
||||
/// Note that the `@Published` property is class-constrained.
|
||||
/// Use it with properties of classes, not with non-class types like structures.
|
||||
@propertyWrapper public struct Published<Value> {
|
||||
|
||||
/// Initialize the storage of the Published
|
||||
/// property as well as the corresponding `Publisher`.
|
||||
public init(initialValue: Value) {
|
||||
value = initialValue
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
public init(wrappedValue: Value) {
|
||||
value = wrappedValue
|
||||
}
|
||||
|
||||
/// A publisher for properties marked with the `@Published` attribute.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Value
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// This function is called to attach the specified
|
||||
/// `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Value, Downstream.Failure == Never
|
||||
{
|
||||
subject.subscribe(subscriber)
|
||||
}
|
||||
|
||||
fileprivate let subject: OpenCombine.CurrentValueSubject<Value, Never>
|
||||
|
||||
fileprivate init(_ output: Output) {
|
||||
subject = .init(output)
|
||||
}
|
||||
}
|
||||
|
||||
private var value: Value
|
||||
|
||||
/// The property that can be accessed with the
|
||||
/// `$` syntax and allows access to the `Publisher`
|
||||
public var projectedValue: Publisher {
|
||||
mutating get {
|
||||
if let publisher = publisher {
|
||||
return publisher
|
||||
}
|
||||
let publisher = Publisher(value)
|
||||
self.publisher = publisher
|
||||
return publisher
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable, message:
|
||||
"@Published is only available on properties of classes")
|
||||
|
||||
public var wrappedValue: Value {
|
||||
get { value }
|
||||
set {
|
||||
value = newValue
|
||||
publisher?.subject.value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var publisher: Publisher?
|
||||
|
||||
@available(*, unavailable, message:
|
||||
"This subscript is unavailable in OpenCombine yet")
|
||||
public static subscript<EnclosingSelf: AnyObject>(
|
||||
_enclosingInstance object: EnclosingSelf,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
|
||||
) -> Value {
|
||||
get { fatalError() }
|
||||
set { fatalError() }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -36,8 +36,8 @@ public protocol Publisher {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
@@ -52,9 +52,19 @@ extension Publisher {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`. After attaching,
|
||||
/// the subscriber can start to receive values.
|
||||
public func subscribe<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
{
|
||||
receive(subscriber: subscriber)
|
||||
}
|
||||
|
||||
public func subscribe<Subject: OpenCombine.Subject>(
|
||||
_ subject: Subject
|
||||
) -> AnyCancellable
|
||||
where Failure == Subject.Failure, Output == Subject.Output
|
||||
{
|
||||
let subscriber = SubjectSubscriber(subject)
|
||||
self.subscribe(subscriber)
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Deferred.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/7/19.
|
||||
//
|
||||
|
||||
/// A publisher that awaits subscription before running the supplied closure
|
||||
/// to create a publisher for the new subscriber.
|
||||
public struct Deferred<DeferredPublisher: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = DeferredPublisher.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = DeferredPublisher.Failure
|
||||
|
||||
/// The closure to execute when it receives a subscription.
|
||||
///
|
||||
/// The publisher returned by this closure immediately
|
||||
/// receives the incoming subscription.
|
||||
public let createPublisher: () -> DeferredPublisher
|
||||
|
||||
/// Creates a deferred publisher.
|
||||
///
|
||||
/// - Parameter createPublisher: The closure to execute
|
||||
/// when calling `subscribe(_:)`.
|
||||
public init(createPublisher: @escaping () -> DeferredPublisher) {
|
||||
self.createPublisher = createPublisher
|
||||
}
|
||||
|
||||
/// 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 Failure == Downstream.Failure,
|
||||
Output == Downstream.Input
|
||||
{
|
||||
let deferredPublisher = createPublisher()
|
||||
deferredPublisher.subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Empty.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
/// A publisher that never publishes any values, and optionally finishes immediately.
|
||||
///
|
||||
/// You can create a ”Never” publisher — one which never sends values and never
|
||||
/// finishes or fails — with the initializer `Empty(completeImmediately: false)`.
|
||||
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
|
||||
/// Creates an empty publisher.
|
||||
///
|
||||
/// - Parameter completeImmediately: A Boolean value that indicates whether
|
||||
/// the publisher should immediately finish.
|
||||
public init(completeImmediately: Bool = true) {
|
||||
self.completeImmediately = completeImmediately
|
||||
}
|
||||
|
||||
/// Creates an empty publisher with the given completion behavior and output and
|
||||
/// failure types.
|
||||
///
|
||||
/// Use this initializer to connect the empty publisher to subscribers or other
|
||||
/// publishers that have specific output and failure types.
|
||||
/// - Parameters:
|
||||
/// - completeImmediately: A Boolean value that indicates whether the publisher
|
||||
/// should immediately finish.
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failureType: The failure type exposed by this publisher.
|
||||
public init(completeImmediately: Bool = true,
|
||||
outputType: Output.Type,
|
||||
failureType: Failure.Type) {
|
||||
self.init(completeImmediately: completeImmediately)
|
||||
}
|
||||
|
||||
/// A Boolean value that indicates whether the publisher immediately sends
|
||||
/// a completion.
|
||||
///
|
||||
/// If `true`, the publisher finishes immediately after sending a subscription
|
||||
/// to the subscriber. If `false`, it never completes.
|
||||
public let completeImmediately: Bool
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
if completeImmediately {
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Fail.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 19.06.2019.
|
||||
//
|
||||
|
||||
/// A publisher that immediately terminates with the specified error.
|
||||
public struct Fail<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// Creates a publisher that immediately terminates with the specified failure.
|
||||
///
|
||||
/// - Parameter error: The failure to send when terminating the publisher.
|
||||
public init(error: Failure) {
|
||||
self.error = error
|
||||
}
|
||||
|
||||
/// Creates publisher with the given output type, that immediately terminates with
|
||||
/// the specified failure.
|
||||
///
|
||||
/// Use this initializer to create a `Fail` publisher that can work with
|
||||
/// subscribers or publishers that expect a given output type.
|
||||
/// - Parameters:
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failure: The failure to send when terminating the publisher.
|
||||
public init(outputType: Output.Type, failure: Failure) {
|
||||
self.error = failure
|
||||
}
|
||||
|
||||
/// The failure to send when terminating the publisher.
|
||||
public let error: Failure
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
extension Fail: Equatable where Failure: Equatable {}
|
||||
@@ -0,0 +1,316 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ┃
|
||||
// ┃ Auto-generated from GYB template. DO NOT EDIT! ┃
|
||||
// ┃ ┃
|
||||
// ┃ ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher that publishes the values of a keyt path as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath: The key path of a property on `Output`
|
||||
/// - Returns: A publisher that publishes the value of the key path.
|
||||
public func map<Result>(
|
||||
_ keyPath: KeyPath<Output, Result>
|
||||
) -> Publishers.MapKeyPath<Self, Result> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath: keyPath
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of two key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
|
||||
public func map<Result0, Result1>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
_ keyPath1: KeyPath<Output, Result1>
|
||||
) -> Publishers.MapKeyPath2<Self, Result0, Result1> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath0: keyPath0,
|
||||
keyPath1: keyPath1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - keyPath2: The key path of a third property on `Output`
|
||||
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
|
||||
public func map<Result0, Result1, Result2>(
|
||||
_ keyPath0: KeyPath<Output, Result0>,
|
||||
_ keyPath1: KeyPath<Output, Result1>,
|
||||
_ keyPath2: KeyPath<Output, Result2>
|
||||
) -> Publishers.MapKeyPath3<Self, Result0, Result1, Result2> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
keyPath0: keyPath0,
|
||||
keyPath1: keyPath1,
|
||||
keyPath2: keyPath2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the value of a key path.
|
||||
public struct MapKeyPath<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath: KeyPath<Upstream.Output, Output>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of two key paths as a tuple.
|
||||
public struct MapKeyPath2<Upstream: Publisher, Output0, Output1>: Publisher {
|
||||
|
||||
public typealias Output = (Output0, Output1)
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of three key paths as a tuple.
|
||||
public struct MapKeyPath3<Upstream: Publisher, Output0, Output1, Output2>: Publisher {
|
||||
|
||||
public typealias Output = (Output0, Output1, Output2)
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
/// The key path of a third property to publish.
|
||||
public let keyPath2: KeyPath<Upstream.Output, Output2>
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath: KeyPath<Input, Output>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath<Upstream, Output>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath = parent.keyPath
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKey" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("keyPath", keyPath),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath2 {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath0: KeyPath<Input, Output0>
|
||||
|
||||
private let keyPath1: KeyPath<Input, Output1>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath2<Upstream, Output0, Output1>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath0 = parent.keyPath0
|
||||
self.keyPath1 = parent.keyPath1
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath0],
|
||||
input[keyPath: keyPath1]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKeys" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("keyPath0", keyPath0),
|
||||
("keyPath1", keyPath1),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapKeyPath3 {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let keyPath0: KeyPath<Input, Output0>
|
||||
|
||||
private let keyPath1: KeyPath<Input, Output1>
|
||||
|
||||
private let keyPath2: KeyPath<Input, Output2>
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.MapKeyPath3<Upstream, Output0, Output1, Output2>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.keyPath0 = parent.keyPath0
|
||||
self.keyPath1 = parent.keyPath1
|
||||
self.keyPath2 = parent.keyPath2
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let output = (
|
||||
input[keyPath: keyPath0],
|
||||
input[keyPath: keyPath1],
|
||||
input[keyPath: keyPath2]
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "ValueForKeys" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("keyPath0", keyPath0),
|
||||
("keyPath1", keyPath1),
|
||||
("keyPath2", keyPath2),
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@
|
||||
/// is also useful when replacing a value with `Catch`.
|
||||
///
|
||||
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
|
||||
/// In contrast with `Publishers.Optional`, a `Just` publisher always produces
|
||||
/// a value.
|
||||
public struct Just<Output>: Publisher {
|
||||
|
||||
public typealias Failure = Never
|
||||
@@ -27,8 +25,8 @@ public struct Just<Output>: Publisher {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubscriberType.Input == Output, SubscriberType.Failure == Never
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Never
|
||||
{
|
||||
subscriber.receive(subscription: Inner(value: output, downstream: subscriber))
|
||||
}
|
||||
@@ -66,8 +64,8 @@ extension Just {
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(Result { try predicate(output) })
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try predicate(output) })
|
||||
}
|
||||
|
||||
public func contains(where predicate: (Output) -> Bool) -> Just<Bool> {
|
||||
@@ -76,8 +74,8 @@ extension Just {
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(Result { try predicate(output) })
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try predicate(output) })
|
||||
}
|
||||
|
||||
public func collect() -> Just<[Output]> {
|
||||
@@ -90,45 +88,25 @@ extension Just {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Just<Output> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
|
||||
}
|
||||
|
||||
public func count() -> Just<Int> {
|
||||
return .init(1)
|
||||
}
|
||||
|
||||
public func dropFirst(
|
||||
_ count: Int = 1
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return Publishers.Optional(count > 0 ? nil : output)
|
||||
return .init(count > 0 ? nil : self.output)
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? nil : output)
|
||||
}
|
||||
|
||||
public func tryDrop(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? nil : output })
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? nil : output)
|
||||
}
|
||||
|
||||
public func first() -> Just<Output> {
|
||||
@@ -137,14 +115,8 @@ extension Just {
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? output : nil })
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func last() -> Just<Output> {
|
||||
@@ -153,18 +125,12 @@ extension Just {
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? output : nil)
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? output : nil })
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Output, Never> {
|
||||
return Publishers.Empty()
|
||||
public func ignoreOutput() -> Empty<Output, Never> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
@@ -175,79 +141,61 @@ extension Just {
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(Result { try transform(output) })
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(Result { try transform(output) })
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Never> {
|
||||
return Publishers.Optional(transform(output))
|
||||
}
|
||||
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(Result { try transform(output) })
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(transform(output))
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(isIncluded(output) ? output : nil)
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(isIncluded(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try isIncluded(output) ? output : nil })
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Publishers.Optional<Output, Never> {
|
||||
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return Publishers.Optional(index == 0 ? output : nil)
|
||||
return .init(index == 0 ? output : nil)
|
||||
}
|
||||
|
||||
public func output<RangeExpr: RangeExpression>(
|
||||
in range: RangeExpr
|
||||
) -> Publishers.Optional<Output, Never> where RangeExpr.Bound == Int {
|
||||
public func output<RangeExpression: Swift.RangeExpression>(
|
||||
in range: RangeExpression
|
||||
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
let range = range.relative(to: 0..<Int.max)
|
||||
return Publishers.Optional(range.lowerBound == 0 ? output : nil)
|
||||
return .init(range.lowerBound == 0 ? output : nil)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually probably should be just this:
|
||||
// return Publishers.Optional(range.contains(0) ? output : nil)
|
||||
// return .init(range.contains(0) ? output : nil)
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Never> {
|
||||
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
return Publishers.Optional(maxLength > 0 ? output : nil)
|
||||
return .init(maxLength > 0 ? output : nil)
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryPrefix(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? output : nil })
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return Publishers.Once(output)
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return .init(output)
|
||||
}
|
||||
|
||||
public func mapError<Failure: Error>(
|
||||
_ transform: (Never) -> Failure
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return Publishers.Once(output)
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return .init(output)
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
@@ -258,9 +206,8 @@ extension Just {
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Error> {
|
||||
return Publishers
|
||||
.Once(Result { try _ = predicate(output, output); return output })
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(Result { try _ = predicate(output, output); return output })
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Just<Output> {
|
||||
@@ -275,66 +222,72 @@ extension Just {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry() -> Just<Output> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Never> {
|
||||
return Publishers.Once(nextPartialResult(initialResult, output))
|
||||
) -> Result<Accumulator, Never>.OCombine.Publisher {
|
||||
return .init(nextPartialResult(initialResult, output))
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Error> {
|
||||
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
|
||||
) -> Result<Accumulator, Error>.OCombine.Publisher {
|
||||
return .init(Result { try nextPartialResult(initialResult, output) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Never> {
|
||||
return Publishers.Once(nextPartialResult(initialResult, output))
|
||||
) -> Result<ElementOfResult, Never>.OCombine.Publisher {
|
||||
return .init(nextPartialResult(initialResult, output))
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(Result { try nextPartialResult(initialResult, output) })
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
extension Just {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
private var downstream: Downstream?
|
||||
private let value: Output
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
fileprivate init(value: Output, downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
_ = downstream.receive(value)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
var description: String { return "Just" }
|
||||
func cancel() {
|
||||
downstream = nil
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
var description: String { return "Just" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(value))
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
|
||||
internal var downstream: Downstream
|
||||
internal var downstream: Downstream?
|
||||
internal var upstreamSubscription: Subscription?
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
@@ -20,5 +20,6 @@ internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
|
||||
internal func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
//
|
||||
// Optional.Publisher.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Optional {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Combine extends `Optional` with a nested type `Publisher`.
|
||||
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
|
||||
/// e. g. when importing Foundation), you will not be able to write
|
||||
/// `Optional<Int>.Publisher`, because Swift is unable to understand
|
||||
/// which `Publisher` you're referring to.
|
||||
///
|
||||
/// So you have to write `Optional<Int>.OCombine.Publisher`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine {
|
||||
|
||||
fileprivate let optional: Optional
|
||||
|
||||
fileprivate init(_ optional: Optional) {
|
||||
self.optional = optional
|
||||
}
|
||||
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
public typealias Output = Wrapped
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let output: Wrapped?
|
||||
|
||||
/// Creates a publisher to emit the optional value of a successful result,
|
||||
/// or fail with an error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ output: Output?) {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
if let output = output {
|
||||
subscriber.receive(subscription: Inner(value: output,
|
||||
downstream: subscriber))
|
||||
} else {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
#endif
|
||||
}
|
||||
|
||||
extension Optional.OCombine {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Wrapped
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
private var downstream: Downstream?
|
||||
private let output: Wrapped
|
||||
|
||||
init(value: Wrapped, downstream: Downstream) {
|
||||
self.output = value
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Optional" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional.OCombine.Publisher: Equatable where Wrapped: Equatable {}
|
||||
|
||||
extension Optional.OCombine.Publisher where Wrapped: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Optional<Bool>.OCombine.Publisher {
|
||||
return .init(self.output.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Optional<Wrapped>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional.OCombine.Publisher where Wrapped: Comparable {
|
||||
|
||||
public func min() -> Optional<Wrapped>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Optional<Wrapped>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional.OCombine.Publisher {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Optional<Bool>.OCombine.Publisher {
|
||||
return .init(self.output.map(predicate))
|
||||
}
|
||||
|
||||
public func collect() -> Optional<[Output]>.OCombine.Publisher {
|
||||
return .init(self.output.map { [$0] } ?? [])
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(self.output.flatMap(transform))
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Optional<Bool>.OCombine.Publisher {
|
||||
return .init(self.output.map(predicate))
|
||||
}
|
||||
|
||||
public func count() -> Optional<Int>.OCombine.Publisher {
|
||||
return .init(self.output.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return .init(self.output.flatMap { count == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(self.output.flatMap { predicate($0) ? nil : $0 })
|
||||
}
|
||||
|
||||
public func first() -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func last() -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { isIncluded($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Empty<Output, Failure> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(output.map(transform))
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return .init(output.flatMap { index == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func output<RangeExpression: Swift.RangeExpression>(
|
||||
in range: RangeExpression
|
||||
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
|
||||
let range = range.relative(to: 0 ..< Int.max)
|
||||
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
|
||||
|
||||
// I don't know why, but Combine has this precondition
|
||||
precondition(range.upperBound < .max - 1)
|
||||
return .init(output.flatMap { range.contains(0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
return .init(output.flatMap { maxLength > 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Optional<Accumulator>.OCombine.Publisher {
|
||||
return .init(output.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(output.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func replaceEmpty(with output: Output) -> Just<Output> {
|
||||
return .init(self.output ?? output)
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// Publishers.AllSatisfy.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes a single Boolean value that indicates whether all received elements pass
|
||||
/// a given predicate.
|
||||
///
|
||||
/// When this publisher receives an element, it runs the predicate against
|
||||
/// the element. If the predicate returns `false`, the publisher produces a `false`
|
||||
/// value and finishes. If the upstream publisher finishes normally, this publisher
|
||||
/// produces a `true` value and finishes.
|
||||
/// As a `reduce`-style operator, this publisher produces at most one value.
|
||||
/// Backpressure note: Upon receiving any request greater than zero, this publisher
|
||||
/// requests unlimited elements from the upstream publisher.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element.
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
/// - Returns: A publisher that publishes a Boolean value that indicates whether
|
||||
/// all received elements pass a given predicate.
|
||||
public func allSatisfy(
|
||||
_ predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.AllSatisfy<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes a single Boolean value that indicates whether all received elements pass
|
||||
/// a given error-throwing predicate.
|
||||
///
|
||||
/// When this publisher receives an element, it runs the predicate against
|
||||
/// the element. If the predicate returns `false`, the publisher produces a `false`
|
||||
/// value and finishes. If the upstream publisher finishes normally, this publisher
|
||||
/// produces a `true` value and finishes. If the predicate throws an error,
|
||||
/// the publisher fails, passing the error to its downstream.
|
||||
/// As a `reduce`-style operator, this publisher produces at most one value.
|
||||
/// Backpressure note: Upon receiving any request greater than zero, this publisher
|
||||
/// requests unlimited elements from the upstream publisher.
|
||||
///
|
||||
/// - Parameter predicate: A closure that evaluates each received element.
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
/// The closure may throw, in which case the publisher cancels the upstream
|
||||
/// publisher and fails with the thrown error.
|
||||
/// - Returns: A publisher that publishes a Boolean value that indicates whether
|
||||
/// all received elements pass a given predicate.
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryAllSatisfy<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes a single Boolean value that indicates whether
|
||||
/// all received elements pass a given predicate.
|
||||
public struct AllSatisfy<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that evaluates each received element.
|
||||
///
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and finish.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that publishes a single Boolean value that indicates whether
|
||||
/// all received elements pass a given error-throwing predicate.
|
||||
public struct TryAllSatisfy<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that evaluates each received element.
|
||||
///
|
||||
/// Return `true` to continue, or `false` to cancel the upstream and complete.
|
||||
/// The closure may throw, in which case the publisher cancels the upstream
|
||||
/// publisher and fails with the thrown error.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Error, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.AllSatisfy {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Downstream.Input == Output, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: true, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if !reduce(newValue) {
|
||||
result = false
|
||||
return .finished
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "AllSatisfy" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryAllSatisfy {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: true, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if try !reduce(newValue) {
|
||||
result = false
|
||||
return .finished
|
||||
}
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "TryAllSatisfy" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// Publishers.Autoconnect.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension ConnectablePublisher {
|
||||
|
||||
/// Automates the process of connecting or disconnecting from this connectable
|
||||
/// publisher.
|
||||
///
|
||||
/// Use `autoconnect()` to simplify working with `ConnectablePublisher` instances,
|
||||
/// such as those created with `makeConnectable()`.
|
||||
///
|
||||
/// let autoconnectedPublisher = somePublisher
|
||||
/// .makeConnectable()
|
||||
/// .autoconnect()
|
||||
/// .subscribe(someSubscriber)
|
||||
///
|
||||
/// - Returns: A publisher which automatically connects to its upstream connectable
|
||||
/// publisher.
|
||||
public func autoconnect() -> Publishers.Autoconnect<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that automatically connects and disconnects from this connectable
|
||||
/// publisher.
|
||||
public class Autoconnect<Upstream: ConnectablePublisher>: Publisher {
|
||||
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case disconnected
|
||||
case connected(refcount: Int, connection: Cancellable)
|
||||
}
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public final let upstream: Upstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state = State.disconnected
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(parent: self, downstream: subscriber)
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .connected(refcount, connection):
|
||||
state = .connected(refcount: refcount + 1, connection: connection)
|
||||
lock.unlock()
|
||||
upstream.subscribe(inner)
|
||||
case .disconnected:
|
||||
lock.unlock()
|
||||
upstream.subscribe(inner)
|
||||
let connection = upstream.connect()
|
||||
lock.lock()
|
||||
state = .connected(refcount: 1, connection: connection)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func willCancel() {
|
||||
lock.lock()
|
||||
switch state {
|
||||
case let .connected(refcount, connection):
|
||||
if refcount <= 1 {
|
||||
self.state = .disconnected
|
||||
lock.unlock()
|
||||
connection.cancel()
|
||||
} else {
|
||||
state = .connected(refcount: refcount - 1, connection: connection)
|
||||
lock.unlock()
|
||||
}
|
||||
case .disconnected:
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Autoconnect {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
fileprivate let combineIdentifier = CombineIdentifier()
|
||||
|
||||
private let parent: Publishers.Autoconnect<Upstream>
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
fileprivate init(parent: Publishers.Autoconnect<Upstream>,
|
||||
downstream: Downstream) {
|
||||
self.parent = parent
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
let sideEffectSubscription = SideEffectSubscription(subscription,
|
||||
parent: parent)
|
||||
downstream.receive(subscription: sideEffectSubscription)
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
fileprivate var description: String { return "Autoconnect" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("parent", parent),
|
||||
("downstream", downstream)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
}
|
||||
|
||||
private struct SideEffectSubscription
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private let parent: Publishers.Autoconnect<Upstream>
|
||||
|
||||
private let upstreamSubscription: Subscription
|
||||
|
||||
fileprivate init(_ upstreamSubscription: Subscription,
|
||||
parent: Publishers.Autoconnect<Upstream>) {
|
||||
self.parent = parent
|
||||
self.upstreamSubscription = upstreamSubscription
|
||||
}
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription.request(demand)
|
||||
}
|
||||
|
||||
fileprivate func cancel() {
|
||||
parent.willCancel()
|
||||
upstreamSubscription.cancel()
|
||||
}
|
||||
|
||||
fileprivate var combineIdentifier: CombineIdentifier {
|
||||
return upstreamSubscription.combineIdentifier
|
||||
}
|
||||
|
||||
fileprivate var description: String {
|
||||
return String(describing: upstreamSubscription)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any {
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Publishers.Collect.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Collects all received elements, and emits a single array of the collection when
|
||||
/// the upstream publisher finishes.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher forwards the error
|
||||
/// to the downstream receiver instead of sending its output.
|
||||
/// This publisher requests an unlimited number of elements from the upstream
|
||||
/// publisher. It only sends the collected array to its downstream after a request
|
||||
/// whose demand is greater than 0 items.
|
||||
/// Note: This publisher uses an unbounded amount of memory to store the received
|
||||
/// values.
|
||||
///
|
||||
/// - Returns: A publisher that collects all received items and returns them as
|
||||
/// an array upon completion.
|
||||
public func collect() -> Publishers.Collect<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that buffers items.
|
||||
public struct Collect<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = [Upstream.Output]
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Downstream.Input == [Upstream.Output]
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Collect: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Collect {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
[Upstream.Output],
|
||||
Upstream.Failure,
|
||||
Void>
|
||||
where Downstream.Input == [Upstream.Output],
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: [], reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result!.append(newValue)
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "Collect"
|
||||
}
|
||||
|
||||
override var customMirror: Mirror {
|
||||
let children: CollectionOfOne<Mirror.Child> = .init(("count", result!.count))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// Publishers.CompactMap.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 11.07.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes all non-`nil` results of calling a closure
|
||||
/// with each received element.
|
||||
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives values from the upstream publisher
|
||||
/// and returns optional values.
|
||||
public let transform: (Upstream.Output) -> Output?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) -> Output?) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes all non-`nil` results of calling an error-throwing
|
||||
/// closure with each received element.
|
||||
public struct TryCompactMap<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// An error-throwing closure that receives values from the upstream publisher
|
||||
/// and returns optional values.
|
||||
///
|
||||
/// If this closure throws an error, the publisher fails.
|
||||
public let transform: (Upstream.Output) throws -> Output?
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) throws -> Output?) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Calls a closure with each received element and publishes any returned
|
||||
/// optional that has a value.
|
||||
///
|
||||
/// - Parameter transform: A closure that receives a value and returns
|
||||
/// an optional value.
|
||||
/// - Returns: A publisher that republishes all non-`nil` results of calling
|
||||
/// the transform closure.
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult?
|
||||
) -> Publishers.CompactMap<Self, ElementOfResult> {
|
||||
return .init(upstream: self, transform: transform)
|
||||
}
|
||||
|
||||
/// Calls an error-throwing closure with each received element and publishes
|
||||
/// any returned optional that has a value.
|
||||
///
|
||||
/// If the closure throws an error, the publisher cancels the upstream and sends
|
||||
/// the thrown error to the downstream receiver as a `Failure`.
|
||||
///
|
||||
/// - Parameter transform: an error-throwing closure that receives a value
|
||||
/// and returns an optional value.
|
||||
/// - Returns: A publisher that republishes all non-`nil` results of calling
|
||||
/// the `transform` closure.
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
|
||||
return .init(upstream: self, transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.CompactMap {
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult?
|
||||
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
|
||||
return .init(upstream: upstream,
|
||||
transform: { self.transform($0).flatMap(transform) })
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: @escaping (Output) -> ElementOfResult
|
||||
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
|
||||
return .init(upstream: upstream,
|
||||
transform: { self.transform($0).map(transform) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCompactMap {
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: @escaping (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.TryCompactMap<Upstream, ElementOfResult> {
|
||||
return .init(upstream: upstream,
|
||||
transform: { try self.transform($0).flatMap(transform) })
|
||||
}
|
||||
}
|
||||
|
||||
private class _CompactMap<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscription
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Transform = (Input) -> Result<Downstream.Input?, Downstream.Failure>
|
||||
|
||||
fileprivate var _transform: Transform?
|
||||
|
||||
var _isCompleted: Bool {
|
||||
return _transform == nil
|
||||
}
|
||||
|
||||
init(downstream: Downstream, transform: @escaping Transform) {
|
||||
_transform = transform
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
guard let transform = _transform else { return .none }
|
||||
|
||||
switch transform(input) {
|
||||
case .success(let output?):
|
||||
return downstream.receive(output)
|
||||
case .success(nil):
|
||||
return .max(1)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
_transform = nil
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard !_isCompleted else { return }
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
_transform = nil
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.CompactMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _CompactMap<Upstream, Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
var description: String { return "CompactMap" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if !_isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryCompactMap {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _CompactMap<Upstream, Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Downstream.Failure == Error
|
||||
{
|
||||
var description: String { return "TryCompactMap" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if !_isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// Publishers.Comparison.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Ilija Puaca on 22/7/19.
|
||||
//
|
||||
|
||||
extension Publisher where Output: Comparable {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min() -> Publishers.Comparison<Self> {
|
||||
return max(by: >)
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max() -> Publishers.Comparison<Self> {
|
||||
return max(by: <)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they are in increasing order.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.Comparison<Self> {
|
||||
return max(by: { areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, using the
|
||||
/// provided error-throwing closure to order the items.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they are in increasing order. If this closure throws, the
|
||||
/// publisher terminates with a `Failure`.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
return tryMax(by: { try areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided ordering closure.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they are in increasing order.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.Comparison<Self> {
|
||||
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided error-throwing closure to order the items.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they are in increasing order. If this closure throws, the
|
||||
/// publisher terminates with a `Failure`.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: @escaping (Self.Output, Self.Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is
|
||||
/// in increasing order from the previously-published item.
|
||||
public struct Comparison<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in
|
||||
/// increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) -> Bool
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.areInIncreasingOrder = areInIncreasingOrder
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
areInIncreasingOrder: areInIncreasingOrder)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is
|
||||
/// in increasing order from the previously-published item, and fails if the ordering
|
||||
/// logic throws an error.
|
||||
public struct TryComparison<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in
|
||||
/// increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
areInIncreasingOrder:
|
||||
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.areInIncreasingOrder = areInIncreasingOrder
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
areInIncreasingOrder: areInIncreasingOrder)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Comparison {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output, Upstream.Output) -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
|
||||
) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if let result = self.result {
|
||||
if reduce(result, newValue) {
|
||||
self.result = newValue
|
||||
}
|
||||
} else {
|
||||
self.result = newValue
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "Comparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryComparison {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output, Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
areInIncreasingOrder:
|
||||
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if let result = self.result {
|
||||
if try reduce(result, newValue) {
|
||||
self.result = newValue
|
||||
}
|
||||
} else {
|
||||
self.result = newValue
|
||||
}
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "TryComparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// Publishers.Contains.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher where Output: Equatable {
|
||||
|
||||
/// Publishes a Boolean value upon receiving an element equal to the argument.
|
||||
///
|
||||
/// The contains publisher consumes all received elements until the upstream publisher
|
||||
/// produces a matching element. At that point, it emits `true` and finishes normally.
|
||||
/// If the upstream finishes normally without producing a matching element,
|
||||
/// this publisher emits `false`, then finishes.
|
||||
///
|
||||
/// - Parameter output: An element to match against.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func contains(_ output: Output) -> Publishers.Contains<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes a Boolean value upon receiving an element that satisfies the predicate
|
||||
/// closure.
|
||||
///
|
||||
/// This operator consumes elements produced from the upstream publisher until
|
||||
/// the upstream publisher produces a matching element.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether the element satisfies the closure’s
|
||||
/// comparison logic.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func contains(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.ContainsWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes a Boolean value upon receiving an element that satisfies
|
||||
/// the throwing predicate closure.
|
||||
///
|
||||
/// This operator consumes elements produced from the upstream publisher until
|
||||
/// the upstream publisher produces a matching element. If the closure throws,
|
||||
/// the stream fails with an error.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether the element satisfies the closure’s
|
||||
/// comparison logic.
|
||||
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
|
||||
/// publisher emits a matching value.
|
||||
public func tryContains(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryContainsWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that emits a Boolean value when a specified element is received from
|
||||
/// its upstream publisher.
|
||||
public struct Contains<Upstream: Publisher>: Publisher
|
||||
where Upstream.Output: Equatable
|
||||
{
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The element to scan for in the upstream publisher.
|
||||
public let output: Upstream.Output
|
||||
|
||||
public init(upstream: Upstream, output: Upstream.Output) {
|
||||
self.upstream = upstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, output: output))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that emits a Boolean value upon receiving an element that satisfies
|
||||
/// the predicate closure.
|
||||
public struct ContainsWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that determines whether the publisher should consider an element
|
||||
/// as a match.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that emits a Boolean value upon receiving an element that satisfies
|
||||
/// the throwing predicate closure.
|
||||
public struct TryContainsWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Bool
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether this publisher should
|
||||
/// emit a `true` element.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Error, Downstream.Input == Bool
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Contains {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream, Upstream.Output, Bool, Upstream.Failure, Void>
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
private let output: Upstream.Output
|
||||
|
||||
fileprivate init(downstream: Downstream, output: Upstream.Output) {
|
||||
self.output = output
|
||||
super.init(downstream: downstream, initial: false, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if newValue == output {
|
||||
result = true
|
||||
return .finished
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Contains" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Contains : Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.ContainsWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output, Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: false, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if reduce(newValue) {
|
||||
result = true
|
||||
return .finished
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "ContainsWhere" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryContainsWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output, Bool,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Downstream.Failure == Error, Downstream.Input == Bool
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: false, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if try reduce(newValue) {
|
||||
result = true
|
||||
return .finished
|
||||
}
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "TryContainsWhere" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Publishers.Count.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/25/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self> {
|
||||
return Publishers.Count(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the number of elements received
|
||||
/// from the upstream publisher.
|
||||
public struct Count<Upstream: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Int
|
||||
|
||||
/// 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
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Downstream.Input == Output
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Count: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Count {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream, Upstream.Output, Int, Failure, Void>
|
||||
where Downstream.Input == Int,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: 0, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result! += 1
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Count" }
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,14 @@ extension Publishers {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
let decodeSubscriber = _Decode<Upstream, SubscriberType, Coder>(
|
||||
let decodeSubscriber = _Decode<Upstream, Downstream, Coder>(
|
||||
downstream: subscriber,
|
||||
decoder: _decoder
|
||||
)
|
||||
upstream.receive(subscriber: decodeSubscriber)
|
||||
upstream.subscribe(decodeSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// Publishers.Drop.swift
|
||||
//
|
||||
//
|
||||
// Created by Sven Weidauer on 03.10.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publisher {
|
||||
/// Omits the specified number of elements before republishing subsequent elements.
|
||||
///
|
||||
/// - Parameter count: The number of elements to omit.
|
||||
/// - Returns: A publisher that does not republish the first `count` elements.
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self> {
|
||||
return .init(upstream: self, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
/// A publisher that omits a specified number of elements before republishing
|
||||
/// later elements.
|
||||
public struct Drop<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The number of elements to drop.
|
||||
public let count: Int
|
||||
|
||||
public init(upstream: Upstream, count: Int) {
|
||||
self.upstream = upstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, count: count)
|
||||
upstream.subscribe(inner)
|
||||
subscriber.receive(subscription: inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Drop: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Drop {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var subscription: Subscription?
|
||||
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
|
||||
private var count: Int
|
||||
|
||||
fileprivate init(downstream: Downstream, count: Int) {
|
||||
self.downstream = downstream
|
||||
self.count = count
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard self.subscription == nil else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
self.subscription = subscription
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
let demandToRequestFromUpstream = pendingDemand + count
|
||||
lock.unlock()
|
||||
if demandToRequestFromUpstream > 0 {
|
||||
subscription.request(demandToRequestFromUpstream)
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
// Combine doesn't lock here!
|
||||
if count > 0 {
|
||||
count -= 1
|
||||
return .none
|
||||
}
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
// Combine doesn't lock here!
|
||||
subscription = nil
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
guard let subscription = self.subscription else {
|
||||
self.pendingDemand += demand
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
// Combine doesn't lock here!
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
}
|
||||
|
||||
var description: String { return "Drop" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@ extension Publishers {
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,18 +53,17 @@ extension Publishers {
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, SubscriberType.Failure == Error
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
@@ -74,22 +73,30 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
|
||||
/// The predicate is reset to `nil` as soon as it returns `false`.
|
||||
var predicate: Predicate?
|
||||
|
||||
var demand: Subscribers.Demand = .none
|
||||
var isCompleted = false
|
||||
|
||||
init(downstream: Downstream, predicate: @escaping Predicate) {
|
||||
self.predicate = predicate
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
var description: String { return "DropWhile" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
|
||||
guard upstreamSubscription != nil else {
|
||||
return .none
|
||||
}
|
||||
|
||||
guard let predicate = self.predicate else {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
// NOTE: until the predicate returns false, we will ask the upstream publisher
|
||||
// for elements one by one, no matter how much elements the downstream subscriber
|
||||
// requests.
|
||||
// for elements one by one.
|
||||
//
|
||||
// However, IF the downstream requests anything, we accumulate this demand in the
|
||||
// `demand` property so that later we can provide the downstream with the correct
|
||||
@@ -97,49 +104,31 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
//
|
||||
// As soon as the predicate returns false, we switch to the mode where
|
||||
// we just forward all the requests from the downstream to the upstream.
|
||||
subscription.request(.max(1))
|
||||
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
|
||||
guard let predicate = self.predicate else {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
switch predicate(input) {
|
||||
case .success(true):
|
||||
// See the NOTE above to understand why we return .max(1)
|
||||
return .max(1)
|
||||
case .success(false):
|
||||
// Okay, we hit the first element not satisfying the predicate,
|
||||
// from now on we just republish the values to the downstream.
|
||||
self.predicate = nil
|
||||
|
||||
// The demand that the downstream has requested has been accumulated in the
|
||||
// `demand` property. Now it's time to pay the debt.
|
||||
//
|
||||
// Subtracting 1 for the current value.
|
||||
return demand + downstream.receive(input) - 1
|
||||
return downstream.receive(input)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
isCompleted = true
|
||||
cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if predicate == nil {
|
||||
// If predicate is nil, that means that we have already received a value
|
||||
// that doesn't satisfy the predicate, hence we're in the state where we
|
||||
// just forward each request to the upstream.
|
||||
upstreamSubscription?.request(demand)
|
||||
} else {
|
||||
// Otherwise, as mentioned in the NOTE above, we accumulate all the demand
|
||||
// requested by the downstream until the predicate returns false.
|
||||
self.demand += demand
|
||||
}
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
isCompleted = true
|
||||
// Don't zero out downstream, that's what Combine does (probably a bug)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,11 +136,16 @@ extension Publishers.DropWhile {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _DropWhile<Upstream, Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
var description: String { return "DropWhile" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isCompleted else { return }
|
||||
downstream.receive(completion: completion)
|
||||
isCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,11 +154,16 @@ extension Publishers.TryDropWhile {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _DropWhile<Upstream, Downstream>,
|
||||
Subscriber
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
var description: String { return "TryDropWhile" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isCompleted else { return }
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
isCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
//
|
||||
// Publishers.Empty.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that never publishes any values, and optionally finishes immediately.
|
||||
///
|
||||
/// You can create a ”Never” publisher — one which never sends values and never
|
||||
/// finishes or fails — with the initializer `Empty(completeImmediately: false)`.
|
||||
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
|
||||
/// Creates an empty publisher.
|
||||
///
|
||||
/// - Parameter completeImmediately: A Boolean value that indicates whether
|
||||
/// the publisher should immediately finish.
|
||||
public init(completeImmediately: Bool = true) {
|
||||
self.completeImmediately = completeImmediately
|
||||
}
|
||||
|
||||
/// Creates an empty publisher with the given completion behavior and output and
|
||||
/// failure types.
|
||||
///
|
||||
/// Use this initializer to connect the empty publisher to subscribers or other
|
||||
/// publishers that have specific output and failure types.
|
||||
/// - Parameters:
|
||||
/// - completeImmediately: A Boolean value that indicates whether the publisher
|
||||
/// should immediately finish.
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failureType: The failure type exposed by this publisher.
|
||||
public init(completeImmediately: Bool = true,
|
||||
outputType: Output.Type,
|
||||
failureType: Failure.Type) {
|
||||
self.init(completeImmediately: completeImmediately)
|
||||
}
|
||||
|
||||
/// A Boolean value that indicates whether the publisher immediately sends
|
||||
/// a completion.
|
||||
///
|
||||
/// If `true`, the publisher finishes immediately after sending a subscription
|
||||
/// to the subscriber. If `false`, it never completes.
|
||||
public let completeImmediately: Bool
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
if completeImmediately {
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,14 +37,14 @@ extension Publishers {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
let encodeSubscriber = _Encode<Upstream, SubscriberType, Coder>(
|
||||
let encodeSubscriber = _Encode<Upstream, Downstream, Coder>(
|
||||
downstream: subscriber,
|
||||
encoder: encoder
|
||||
)
|
||||
upstream.receive(subscriber: encodeSubscriber)
|
||||
upstream.subscribe(encodeSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// Publishers.Fail.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 19.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that immediately terminates with the specified error.
|
||||
public struct Fail<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// Creates a publisher that immediately terminates with the specified failure.
|
||||
///
|
||||
/// - Parameter error: The failure to send when terminating the publisher.
|
||||
public init(error: Failure) {
|
||||
self.error = error
|
||||
}
|
||||
|
||||
/// Creates publisher with the given output type, that immediately terminates with
|
||||
/// the specified failure.
|
||||
///
|
||||
/// Use this initializer to create a `Fail` publisher that can work with
|
||||
/// subscribers or publishers that expect a given output type.
|
||||
/// - Parameters:
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failure: The failure to send when terminating the publisher.
|
||||
public init(outputType: Output.Type, failure: Failure) {
|
||||
self.error = failure
|
||||
}
|
||||
|
||||
/// The failure to send when terminating the publisher.
|
||||
public let error: Failure
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Fail: Equatable where Failure: Equatable {}
|
||||
@@ -0,0 +1,228 @@
|
||||
//
|
||||
// Publishers.Filter.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/3/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Republishes all elements that match a provided closure.
|
||||
///
|
||||
/// - Parameter isIncluded: A closure that takes one element and returns
|
||||
/// a Boolean value indicating whether to republish the element.
|
||||
/// - Returns: A publisher that republishes all elements that satisfy the closure.
|
||||
public func filter(
|
||||
_ isIncluded: @escaping (Output) -> Bool
|
||||
) -> Publishers.Filter<Self> {
|
||||
return Publishers.Filter(upstream: self, isIncluded: isIncluded)
|
||||
}
|
||||
|
||||
/// Republishes all elements that match a provided error-throwing closure.
|
||||
///
|
||||
/// If the `isIncluded` closure throws an error, the publisher fails with that error.
|
||||
///
|
||||
/// - Parameter isIncluded: A closure that takes one element and returns a
|
||||
/// Boolean value indicating whether to republish the element.
|
||||
/// - Returns: A publisher that republishes all elements that satisfy the closure.
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Self> {
|
||||
return Publishers.TryFilter(upstream: self, isIncluded: isIncluded)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Filter {
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: @escaping (Output) -> Bool
|
||||
) -> Publishers.Filter<Upstream> {
|
||||
return .init(upstream: upstream) { self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Upstream> {
|
||||
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryFilter {
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: @escaping (Output) -> Bool
|
||||
) -> Publishers.TryFilter<Upstream> {
|
||||
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFilter<Upstream> {
|
||||
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes all elements that match a provided closure.
|
||||
public struct Filter<Upstream: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that indicates whether to republish an element.
|
||||
public let isIncluded: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, isIncluded: @escaping (Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.isIncluded = isIncluded
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let filter = Inner(downstream: subscriber, isIncluded: catching(isIncluded))
|
||||
upstream.receive(subscriber: filter)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes all elements that match
|
||||
/// a provided error-throwing closure.
|
||||
public struct TryFilter<Upstream>: Publisher where Upstream: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A error-throwing closure that indicates whether to republish an element.
|
||||
public let isIncluded: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream,
|
||||
isIncluded: @escaping (Upstream.Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.isIncluded = isIncluded
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
let filter = Inner(downstream: subscriber, isIncluded: catching(isIncluded))
|
||||
upstream.receive(subscriber: filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class _Filter<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscription
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Predicate = (Input) -> Result<Bool, Downstream.Failure>
|
||||
|
||||
private var _isIncluded: Predicate?
|
||||
|
||||
var isFinished: Bool {
|
||||
return _isIncluded == nil
|
||||
}
|
||||
|
||||
init(downstream: Downstream, isIncluded: @escaping Predicate) {
|
||||
_isIncluded = isIncluded
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
guard let isIncluded = _isIncluded else { return .none }
|
||||
switch isIncluded(input) {
|
||||
case .success(let isIncluded):
|
||||
return isIncluded ? downstream.receive(input) : .max(1)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard !isFinished else { return }
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
_isIncluded = nil
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Filter {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Filter<Upstream, Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure {
|
||||
|
||||
var description: String { return "Filter" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isFinished else { return }
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryFilter {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Filter<Upstream, Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error {
|
||||
|
||||
var description: String { return "TryFilter" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isFinished else { return }
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// Publishers.First.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/8/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the first element of a stream, then finishes.
|
||||
///
|
||||
/// If this publisher doesn’t receive any elements, it finishes without publishing.
|
||||
/// - Returns: A publisher that only publishes the first element of a stream.
|
||||
public func first() -> Publishers.First<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
|
||||
/// Publishes the first element of a stream to
|
||||
/// satisfy a predicate closure, then finishes.
|
||||
///
|
||||
/// The publisher ignores all elements after the first.
|
||||
/// If this publisher doesn’t receive any elements,
|
||||
/// it finishes without publishing.
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and
|
||||
/// returns a Boolean value that indicates whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the first element of a stream
|
||||
/// that satifies the predicate.
|
||||
public func first(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.FirstWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Publishes the first element of a stream to satisfy a
|
||||
/// throwing predicate closure, then finishes.
|
||||
///
|
||||
/// The publisher ignores all elements after the first. If this publisher
|
||||
/// doesn’t receive any elements, it finishes without publishing. If the
|
||||
/// predicate closure throws, the publisher fails with an error.
|
||||
/// - Parameter predicate: A closure that takes an element as a parameter and
|
||||
/// returns a Boolean value that indicates whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the first element of a stream
|
||||
/// that satifies the predicate.
|
||||
public func tryFirst(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryFirstWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the first element of a stream, then finishes.
|
||||
public struct First<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the first element of a
|
||||
/// stream to satisfy a predicate closure.
|
||||
public struct FirstWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that determines whether to publish an element.
|
||||
public let predicate: (Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the first element of a stream
|
||||
/// to satisfy a throwing predicate closure.
|
||||
public struct TryFirstWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether to publish an element.
|
||||
public let predicate: (Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.First: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.First {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
Void>
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result = newValue
|
||||
return .finished
|
||||
}
|
||||
|
||||
override var description: String { return "First" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FirstWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream, Output, Output, Failure, (Output) -> Bool>
|
||||
where Upstream.Output == Downstream.Input,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream, predicate: @escaping (Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if reduce(newValue) {
|
||||
result = newValue
|
||||
return .finished
|
||||
} else {
|
||||
return .continue
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryFirst" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryFirstWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Output) throws -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Output
|
||||
) -> PartialCompletion<Void, Error> {
|
||||
do {
|
||||
if try reduce(newValue) {
|
||||
result = newValue
|
||||
return .finished
|
||||
} else {
|
||||
return .continue
|
||||
}
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryFirstWhere" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
//
|
||||
// Publishers.FlatMap.swift
|
||||
//
|
||||
// Created by Eric Patey on 16.08.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms all elements from an upstream publisher into a new or existing
|
||||
/// publisher.
|
||||
///
|
||||
/// `flatMap` merges the output from all returned publishers into a single stream of
|
||||
/// output.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - maxPublishers: The maximum number of publishers produced by this method.
|
||||
/// - transform: A closure that takes an element as a parameter and returns a
|
||||
/// publisher that produces elements of that type.
|
||||
/// - Returns: A publisher that transforms elements from an upstream publisher into
|
||||
/// a publisher of that element’s type.
|
||||
public func flatMap<Result, Child: Publisher>(
|
||||
maxPublishers: Subscribers.Demand = .unlimited,
|
||||
_ transform: @escaping (Output) -> Child
|
||||
) -> Publishers.FlatMap<Child, Self>
|
||||
where Result == Child.Output, Failure == Child.Failure {
|
||||
return Publishers.FlatMap(upstream: self,
|
||||
maxPublishers: maxPublishers,
|
||||
transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct FlatMap<Child: Publisher, Upstream: Publisher>: Publisher
|
||||
where Child.Failure == Upstream.Failure
|
||||
{
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Child.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let maxPublishers: Subscribers.Demand
|
||||
|
||||
public let transform: (Upstream.Output) -> Child
|
||||
|
||||
public init(upstream: Upstream, maxPublishers: Subscribers.Demand,
|
||||
transform: @escaping (Upstream.Output) -> Child) {
|
||||
self.upstream = upstream
|
||||
self.maxPublishers = maxPublishers
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
/// 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 Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
maxPublishers: maxPublishers,
|
||||
transform: transform)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap {
|
||||
|
||||
fileprivate final class Inner<Downstream: Subscriber>
|
||||
: CustomStringConvertible,
|
||||
Cancellable
|
||||
where Downstream.Input == Child.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private typealias PendingValue = (
|
||||
value: Downstream.Input,
|
||||
// If the value was buffered at the time it became available, and the child's
|
||||
// demand was left at `.none` we keep track of the child in `pausedChild` so
|
||||
// that we can demand some more of it after sending this value.
|
||||
pausedChild: ChildSubscriber?
|
||||
)
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
private let maxPublishers: Subscribers.Demand
|
||||
private let transform: (Input) -> Child
|
||||
|
||||
// Locking rules for this class.
|
||||
// - All mutable state must only be accessed while `lock` is held.
|
||||
// - In order to avoid any deadlock potential, it is absolutely forbidden to have
|
||||
// any sort of call out from this class while the lock is held. This is why
|
||||
// the draining of the work queue uses a relatively complex pattern.
|
||||
private var downstream: Downstream?
|
||||
private var childSubscribers = Set<ChildSubscriber>()
|
||||
private var downstreamDemand = Subscribers.Demand.unlimited
|
||||
private var valuesToSend = [PendingValue]()
|
||||
private var queueIsBeingProcessed = false
|
||||
private var sendFinishedAfterDrainingQueue = false
|
||||
private var upstreamSubscription: Subscription?
|
||||
|
||||
var description: String { return "FlatMap" }
|
||||
|
||||
init(downstream: Downstream,
|
||||
maxPublishers: Subscribers.Demand,
|
||||
transform: @escaping (Upstream.Output) -> Child) {
|
||||
self.downstream = downstream
|
||||
self.maxPublishers = maxPublishers
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
final func cancel() {
|
||||
|
||||
let (upstreamToCancel, childrenToCancel) = lock
|
||||
.do { () -> (Subscription?, Set<ChildSubscriber>) in
|
||||
let upstreamToCancel = upstreamSubscription
|
||||
upstreamSubscription = nil
|
||||
return (upstreamToCancel, lockedDeactivateAndReturnChildrenToCancel())
|
||||
}
|
||||
|
||||
upstreamToCancel?.cancel()
|
||||
cancelChildren(childrenToCancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private implementation
|
||||
extension Publishers.FlatMap.Inner {
|
||||
|
||||
private func deactivate() {
|
||||
cancelChildren(lock.do(lockedDeactivateAndReturnChildrenToCancel))
|
||||
}
|
||||
|
||||
// Must be called with lock held.
|
||||
private func lockedDeactivateAndReturnChildrenToCancel() -> Set<ChildSubscriber> {
|
||||
downstream = nil
|
||||
downstreamDemand = .none
|
||||
let result = childSubscribers
|
||||
childSubscribers.removeAll()
|
||||
upstreamSubscription = nil
|
||||
return result
|
||||
}
|
||||
|
||||
private func cancelChildren(_ childrenToCancel: Set<ChildSubscriber>) {
|
||||
childrenToCancel.forEach { $0.cancel() }
|
||||
}
|
||||
|
||||
/// In a thread-safe way, this function performs the passed in work with the lock held
|
||||
/// and then checks to see if either upstream or any of the child subscriptions remain
|
||||
/// active. If there are no remaining active subscriptions, it enqueues the sending
|
||||
/// of `.finished` downstream using the processing queue.
|
||||
/// - Parameter lockedWork: block to be formed with the lock held.
|
||||
private final func maybeSendFinishedAfterExecutingWork(lockedWork: () -> Void) {
|
||||
let shouldProcessQueue: Bool = lock.do {
|
||||
lockedWork()
|
||||
if childSubscribers.isEmpty && upstreamSubscription == nil {
|
||||
sendFinishedAfterDrainingQueue = true
|
||||
if !queueIsBeingProcessed {
|
||||
queueIsBeingProcessed = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if shouldProcessQueue {
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
|
||||
private func receivedCompletion(_ completion: Subscribers.Completion<Failure>,
|
||||
fromChild child: ChildSubscriber) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
removeActiveSubscription(forChild: child)
|
||||
case .failure:
|
||||
downstream?.receive(completion: completion)
|
||||
deactivate()
|
||||
}
|
||||
}
|
||||
|
||||
private func removeActiveSubscription(forChild child: ChildSubscriber) {
|
||||
maybeSendFinishedAfterExecutingWork { childSubscribers.remove(child) }
|
||||
}
|
||||
|
||||
private func receivedValue(_ value: Child.Output,
|
||||
fromChild child: ChildSubscriber) -> Subscribers.Demand {
|
||||
// When receiving a value from a child, we need to determine what additional
|
||||
// demand to return to the child. Apple's logic for this determination is as
|
||||
// follows:
|
||||
// - If we are in `.unlimited` mode, we always request `.none` additional
|
||||
// else
|
||||
// - If there is a surplus relative to the demand, we request `.none`
|
||||
// else
|
||||
// - There is not yet a surplus, so request `.max(1)` more from the child
|
||||
|
||||
let (surplusAvailable, processTheQueue): (Bool, Bool) = lock.do {
|
||||
// If we already have enough values to satisfy the demand, we "buffer" this
|
||||
// child value establishing a surplus.
|
||||
if downstreamDemand <= valuesToSend.count {
|
||||
valuesToSend.append((value, child))
|
||||
return (surplusAvailable: true, processTheQueue: false)
|
||||
} else {
|
||||
valuesToSend.append((value, nil))
|
||||
if queueIsBeingProcessed {
|
||||
return (surplusAvailable: false, processTheQueue: false)
|
||||
}
|
||||
queueIsBeingProcessed = true
|
||||
return (surplusAvailable: false, processTheQueue: true)
|
||||
}
|
||||
}
|
||||
|
||||
let demandResult = surplusAvailable || demandForChild() == .unlimited
|
||||
? Subscribers.Demand.none
|
||||
: .max(1)
|
||||
|
||||
if processTheQueue {
|
||||
processQueue()
|
||||
}
|
||||
|
||||
return demandResult
|
||||
}
|
||||
|
||||
private func demandForChild() -> Subscribers.Demand {
|
||||
return downstreamDemand == .unlimited ? .unlimited : .max(1)
|
||||
}
|
||||
|
||||
private enum QueueWorkStatus {
|
||||
case noWork
|
||||
case sendFinish
|
||||
case sendValues(values: ArraySlice<PendingValue>)
|
||||
}
|
||||
|
||||
private func processQueue() {
|
||||
assert(queueIsBeingProcessed)
|
||||
|
||||
// We loop processing the queue in case somebody put stuff on the queue while we
|
||||
// were sending values with the lock unlocked.
|
||||
while true {
|
||||
let work: QueueWorkStatus = lock.do {
|
||||
if downstreamDemand == .none || valuesToSend.isEmpty {
|
||||
if sendFinishedAfterDrainingQueue && valuesToSend.isEmpty {
|
||||
return .sendFinish
|
||||
} else {
|
||||
queueIsBeingProcessed = false
|
||||
return .noWork
|
||||
}
|
||||
}
|
||||
|
||||
let countToSend = min(valuesToSend.count, downstreamDemand.max ?? .max)
|
||||
let result = valuesToSend[0..<countToSend]
|
||||
// TODO: Consider an alternative storage to avoid O(n) removeFirst
|
||||
valuesToSend.removeFirst(countToSend)
|
||||
downstreamDemand -= countToSend
|
||||
return .sendValues(values: result)
|
||||
}
|
||||
|
||||
guard let downstream = downstream else { return }
|
||||
|
||||
switch work {
|
||||
case .noWork:
|
||||
return
|
||||
case .sendFinish:
|
||||
downstream.receive(completion: .finished)
|
||||
deactivate()
|
||||
return
|
||||
case .sendValues(let values):
|
||||
var newDemand = Subscribers.Demand.none
|
||||
values.forEach {
|
||||
newDemand += downstream.receive($0.value)
|
||||
// pausedChild is present only if the value was buffered and the
|
||||
// child's demand was left at `.none`. In that case, once we send the
|
||||
// buffered value, we need to tell the child to get another value.
|
||||
$0.pausedChild?.request(.max(1))
|
||||
}
|
||||
|
||||
if newDemand != .none {
|
||||
lock.do { downstreamDemand += newDemand }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This `Subscriber` implementation is for `FlatMap`'s upstream subscription
|
||||
extension Publishers.FlatMap.Inner: Subscriber {
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream?.receive(subscription: self)
|
||||
subscription.request(maxPublishers)
|
||||
}
|
||||
|
||||
/// Receive a new value from the upstream subscription. A new child subscription
|
||||
/// will be made on the `Child` that the input value is transformed into.
|
||||
/// - Parameter input: a value to be transformed by `transform`
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
let newChildSubscriber = ChildSubscriber(parent: self)
|
||||
|
||||
lock.do { _ = childSubscribers.insert(newChildSubscriber) }
|
||||
|
||||
self.transform(input).subscribe(newChildSubscriber)
|
||||
|
||||
return .none
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
maybeSendFinishedAfterExecutingWork { upstreamSubscription = nil }
|
||||
case .failure:
|
||||
downstream?.receive(completion: completion)
|
||||
deactivate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inner is the `Subscription` for `Downstream`
|
||||
extension Publishers.FlatMap.Inner: Subscription {
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
let (drainTheQueue, becameUnlimited) = lock.do { () -> (Bool, Bool) in
|
||||
let becameUnlimited = demand == .unlimited && downstreamDemand != .unlimited
|
||||
downstreamDemand = demand
|
||||
defer { queueIsBeingProcessed = true }
|
||||
return (!queueIsBeingProcessed, becameUnlimited)
|
||||
}
|
||||
|
||||
if becameUnlimited {
|
||||
// TODO: This code isn't yet thread safe. The correct change is to do this
|
||||
// through the queue just like sending values and finished. Finished is
|
||||
// done through the queue as a bit of a hack. The right design is to have
|
||||
// an enum of actions on the queue. That enum will include (send value,
|
||||
// send finished, set child demand).
|
||||
let newChildDemand = demandForChild()
|
||||
childSubscribers.forEach { $0.request(newChildDemand) }
|
||||
}
|
||||
|
||||
if drainTheQueue {
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap.Inner {
|
||||
/// ChildSubscriber is needed to help implement the backpressure/demand strategy.
|
||||
/// Specifically, a custom subscriber is needed to manage the demand of the child
|
||||
/// subscription:
|
||||
/// - Send .max(1) request when the subscription is received
|
||||
/// - Send .max(1) request when downstream subscriber demands more and a previously
|
||||
/// buffered value from the child was sent. (When the value was buffered, the
|
||||
/// child's demand reached .none - effectively pausing the child.)
|
||||
fileprivate final class ChildSubscriber: Hashable {
|
||||
internal typealias Input = Downstream.Input
|
||||
internal typealias Failure = Downstream.Failure
|
||||
|
||||
private var _upstreamSubscription: Subscription?
|
||||
private unowned let _parent: Publishers.FlatMap<Child, Upstream>.Inner<Downstream>
|
||||
|
||||
init(parent: Publishers.FlatMap<Child, Upstream>.Inner<Downstream>) {
|
||||
_parent = parent
|
||||
}
|
||||
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
_upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
public static func == (lhs: ChildSubscriber, rhs: ChildSubscriber) -> Bool {
|
||||
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap.Inner.ChildSubscriber: Cancellable {
|
||||
fileprivate func cancel() {
|
||||
_upstreamSubscription?.cancel()
|
||||
_upstreamSubscription = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.FlatMap.Inner.ChildSubscriber: Subscriber {
|
||||
|
||||
fileprivate func receive(subscription: Subscription) {
|
||||
if _upstreamSubscription == nil {
|
||||
_upstreamSubscription = subscription
|
||||
subscription.request(_parent.demandForChild())
|
||||
} else {
|
||||
assertionFailure()
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return _parent.receivedValue(input, fromChild: self)
|
||||
}
|
||||
|
||||
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
|
||||
_parent.receivedCompletion(completion, fromChild: self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// Publishers.IgnoreOutput.swift
|
||||
//
|
||||
// Created by Eric Patey on 16.08.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Ingores all upstream elements, but passes along a completion
|
||||
/// state (finished or failed).
|
||||
///
|
||||
/// The output type of this publisher is `Never`.
|
||||
/// - Returns: A publisher that ignores all upstream elements.
|
||||
public func ignoreOutput() -> Publishers.IgnoreOutput<Self> {
|
||||
return Publishers.IgnoreOutput(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
/// A publisher that ignores all upstream elements, but passes along a completion
|
||||
/// state (finish or failed).
|
||||
public struct IgnoreOutput<Upstream: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Never
|
||||
|
||||
/// 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
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
/// 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 Downstream.Failure == Upstream.Failure, Downstream.Input == Never {
|
||||
let inner = Inner<Downstream>(downstream: subscriber)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.IgnoreOutput {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Downstream.Input == Never,
|
||||
Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Output = Never
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
var description: String { return "IgnoreOutput" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
// ignore and requests from downstream since we'll never send
|
||||
// any values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.IgnoreOutput: Equatable where Upstream: Equatable {}
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// Publishers.Last.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/9/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Only publishes the last element of a stream, after the stream finishes.
|
||||
/// - Returns: A publisher that only publishes the last element of a stream.
|
||||
public func last() -> Publishers.Last<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
|
||||
/// Only publishes the last element of a stream that satisfies a predicate closure,
|
||||
/// after the stream finishes.
|
||||
///
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the last element satisfying
|
||||
/// the given predicate.
|
||||
public func last(
|
||||
where predicate: @escaping (Output) -> Bool
|
||||
) -> Publishers.LastWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
|
||||
/// Only publishes the last element of a stream that satisfies an error-throwing
|
||||
/// predicate closure, after the stream finishes.
|
||||
///
|
||||
/// If the predicate closure throws, the publisher fails with the thrown error.
|
||||
/// - Parameter predicate: A closure that takes an element as its parameter and
|
||||
/// returns a Boolean value indicating whether to publish the element.
|
||||
/// - Returns: A publisher that only publishes the last element satisfying
|
||||
/// the given predicate.
|
||||
public func tryLast(
|
||||
where predicate: @escaping (Output) throws -> Bool
|
||||
) -> Publishers.TryLastWhere<Self> {
|
||||
return .init(upstream: self, predicate: predicate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that only publishes the last element of a stream,
|
||||
/// after the stream finishes.
|
||||
public struct Last<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the last element of a stream that satisfies
|
||||
/// a predicate closure, once the stream finishes.
|
||||
public struct LastWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that determines whether to publish an element.
|
||||
public let predicate: (Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that only publishes the last element of a stream that satisfies
|
||||
/// an error-throwing predicate closure, once the stream finishes.
|
||||
public struct TryLastWhere<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The error-throwing closure that determines whether to publish an element.
|
||||
public let predicate: (Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
|
||||
self.upstream = upstream
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Error, Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Last: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publishers.Last {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
Void>
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: ())
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result = newValue
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Last" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.LastWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if reduce(newValue) {
|
||||
result = newValue
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "LastWhere" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryLastWhere {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output) throws -> Bool>
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(downstream: Downstream,
|
||||
predicate: @escaping (Upstream.Output) throws -> Bool) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: predicate)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if try reduce(newValue) {
|
||||
result = newValue
|
||||
}
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryLastWhere" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Publishers.MakeConnectable.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Creates a connectable wrapper around the publisher.
|
||||
///
|
||||
/// - Returns: A `ConnectablePublisher` wrapping this publisher.
|
||||
public func makeConnectable() -> Publishers.MakeConnectable<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct MakeConnectable<Upstream: Publisher>: ConnectablePublisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
private let inner: Multicast<Upstream, PassthroughSubject<Output, Failure>>
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
inner = upstream.multicast(subject: .init())
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
inner.subscribe(subscriber)
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
return inner.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Anton Nazarov on 25.06.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms all elements from the upstream publisher with a provided closure.
|
||||
@@ -39,7 +41,7 @@ extension Publisher {
|
||||
extension Publishers {
|
||||
/// A publisher that transforms all elements from the upstream publisher with
|
||||
/// a provided closure.
|
||||
public struct Map<Upstream: Publisher, Output> : Publisher {
|
||||
public struct Map<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
@@ -48,6 +50,18 @@ extension Publishers {
|
||||
|
||||
/// The closure that transforms elements from the upstream publisher.
|
||||
public let transform: (Upstream.Output) -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) -> Output) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, map: transform))
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that transforms all elements from the upstream publisher
|
||||
@@ -62,16 +76,16 @@ extension Publishers {
|
||||
/// The error-throwing closure that transforms elements from
|
||||
/// the upstream publisher.
|
||||
public let transform: (Upstream.Output) throws -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) throws -> Output) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Map {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.receive(subscriber: inner)
|
||||
}
|
||||
|
||||
public func map<Result>(
|
||||
_ transform: @escaping (Output) -> Result
|
||||
@@ -91,8 +105,7 @@ extension Publishers.TryMap {
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(Inner(downstream: subscriber, map: transform))
|
||||
}
|
||||
|
||||
public func map<Result>(
|
||||
@@ -108,84 +121,158 @@ extension Publishers.TryMap {
|
||||
}
|
||||
}
|
||||
|
||||
private class _Map<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Transform = (Input) -> Result<Downstream.Input, Downstream.Failure>
|
||||
|
||||
fileprivate var _transform: Transform?
|
||||
|
||||
var isCompleted: Bool {
|
||||
return _transform == nil
|
||||
}
|
||||
|
||||
init(downstream: Downstream, transform: @escaping Transform) {
|
||||
_transform = transform
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
var description: String { return "Map" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
switch _transform?(input) {
|
||||
case .success(let output)?:
|
||||
return downstream.receive(output)
|
||||
case .failure(let error)?:
|
||||
downstream.receive(completion: .failure(error))
|
||||
_transform = nil
|
||||
return .none
|
||||
case nil:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
_transform = nil
|
||||
upstreamSubscription?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Map {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Map<Upstream, Downstream>,
|
||||
Subscriber
|
||||
where Downstream.Failure == Upstream.Failure
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let map: (Input) -> Output
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream, map: @escaping (Input) -> Output) {
|
||||
self.downstream = downstream
|
||||
self.map = map
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(map(input))
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Map" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryMap {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Map<Upstream, Downstream>,
|
||||
Subscriber
|
||||
where Downstream.Failure == Error
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let map: (Input) throws -> Output
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream,
|
||||
map: @escaping (Input) throws -> Output) {
|
||||
self.downstream = downstream
|
||||
self.map = map
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
do {
|
||||
return try downstream.receive(map(input))
|
||||
} catch {
|
||||
lock.lock()
|
||||
let subscription: Subscription?
|
||||
switch status {
|
||||
case let .subscribed(upstreamSubscription):
|
||||
subscription = upstreamSubscription
|
||||
case .awaitingSubscription, .terminal:
|
||||
subscription = nil
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription?.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "TryMap" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Publishers.MapError.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that converts any failure from the
|
||||
/// upstream publisher into a new error.
|
||||
public struct MapError<Upstream: Publisher, Failure: Error>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that converts the upstream failure into a new error.
|
||||
public let transform: (Upstream.Failure) -> Failure
|
||||
|
||||
public init(upstream: Upstream, _ map: @escaping (Upstream.Failure) -> Failure) {
|
||||
self.upstream = upstream
|
||||
self.transform = map
|
||||
}
|
||||
|
||||
/// 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 Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, map: transform))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Converts any failure from the upstream publisher into a new error.
|
||||
///
|
||||
/// Until the upstream publisher finishes normally or fails with an error,
|
||||
/// the returned publisher republishes all the elements it receives.
|
||||
///
|
||||
/// - Parameter transform: A closure that takes the upstream failure as a
|
||||
/// parameter and returns a new error for the publisher to terminate with.
|
||||
/// - Returns: A publisher that replaces any upstream failure with a
|
||||
/// new error produced by the `transform` closure.
|
||||
public func mapError<NewFailure: Error>(
|
||||
_ transform: @escaping (Self.Failure) -> NewFailure
|
||||
) -> Publishers.MapError<Self, NewFailure>
|
||||
{
|
||||
return Publishers.MapError(upstream: self, transform)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.MapError {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
private let map: (Upstream.Failure) -> Downstream.Failure
|
||||
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
var description: String { return "MapError" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
init(downstream: Downstream,
|
||||
map: @escaping (Upstream.Failure) -> Downstream.Failure) {
|
||||
self.downstream = downstream
|
||||
self.map = map
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(map(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
${template_header}
|
||||
//
|
||||
// Publishers.MapKeyPath.swift.gyb
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 03/10/2019.
|
||||
//
|
||||
|
||||
%{
|
||||
from gyb_opencombine_support import (
|
||||
suffix_variadic,
|
||||
list_with_suffix_variadic
|
||||
)
|
||||
|
||||
instantiations = [(1, '', ''),
|
||||
(2, 'two', 'second '),
|
||||
(3, 'three', 'third ')]
|
||||
|
||||
def key_path_var(index, arity):
|
||||
return suffix_variadic('keyPath', index, arity)
|
||||
|
||||
def make_publisher_name(arity):
|
||||
return suffix_variadic('MapKeyPath', arity, arity)
|
||||
|
||||
def make_output_types(arity):
|
||||
return list_with_suffix_variadic('Output', arity)
|
||||
}%
|
||||
extension Publisher {
|
||||
% for arity, cardinal, _ in instantiations:
|
||||
% result_types = list_with_suffix_variadic('Result', arity)
|
||||
% cs_result_types = ', '.join(result_types)
|
||||
%
|
||||
% method_args = \
|
||||
% ['_ {}: KeyPath<Output, {}>'.format(key_path_var(i, arity), result_types[i]) \
|
||||
% for i in range(arity)]
|
||||
% method_args_joined = ',\n '.join(method_args)
|
||||
%
|
||||
% init_args = ['{}: {}'.format(key_path_var(i, arity), key_path_var(i, arity)) \
|
||||
% for i in range(arity)]
|
||||
% init_args_joined = ',\n '.join(init_args)
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
%
|
||||
% doc_cardinal = 'a keyt path' if arity == 1 else cardinal + ' key paths'
|
||||
|
||||
/// Returns a publisher that publishes the values of ${doc_cardinal} as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
% for i in range(arity):
|
||||
% ordinal = 'another ' if i == 1 else 'a ' + instantiations[i][2]
|
||||
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`
|
||||
% end
|
||||
%
|
||||
% doc_comment_suffix = 'value of the key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
/// - Returns: A publisher that publishes the ${doc_comment_suffix}.
|
||||
public func map<${cs_result_types}>(
|
||||
${method_args_joined}
|
||||
) -> Publishers.${publisher_name}<Self, ${cs_result_types}> {
|
||||
return .init(
|
||||
upstream: self,
|
||||
${init_args_joined}
|
||||
)
|
||||
}
|
||||
% end
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
% for arity, cardinal, ordinal in instantiations:
|
||||
%
|
||||
% doc_comment_suffix = 'value of a key path' \
|
||||
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
|
||||
%
|
||||
% output_types = make_output_types(arity)
|
||||
% cs_output_types = ', '.join(output_types)
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
|
||||
/// A publisher that publishes the ${doc_comment_suffix}.
|
||||
public struct ${publisher_name}<Upstream: Publisher, ${cs_output_types}>: Publisher {
|
||||
|
||||
% if arity != 1:
|
||||
public typealias Output = (${cs_output_types})
|
||||
|
||||
% end
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
% for i in range(arity):
|
||||
% ordinal = instantiations[i][2]
|
||||
/// The key path of a ${ordinal}property to publish.
|
||||
public let ${key_path_var(i, arity)}: KeyPath<Upstream.Output, ${output_types[i]}>
|
||||
|
||||
% end
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, parent: self))
|
||||
}
|
||||
}
|
||||
% end
|
||||
}
|
||||
% for arity, _, _ in instantiations:
|
||||
% output_types = make_output_types(arity)
|
||||
% cs_output_types = ', '.join(output_types)
|
||||
%
|
||||
% publisher_name = make_publisher_name(arity)
|
||||
|
||||
extension Publishers.${publisher_name} {
|
||||
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
% for i in range(arity):
|
||||
private let ${key_path_var(i, arity)}: KeyPath<Input, ${output_types[i]}>
|
||||
|
||||
% end
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
parent: Publishers.${publisher_name}<Upstream, ${cs_output_types}>
|
||||
) {
|
||||
self.downstream = downstream
|
||||
% for i in range(arity):
|
||||
self.${key_path_var(i, arity)} = parent.${key_path_var(i, arity)}
|
||||
% end
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
% output_components = \
|
||||
% ['input[keyPath: {}]'.format(key_path_var(i, arity)) for i in range(arity)]
|
||||
% output_components_joined = ',\n '.join(output_components)
|
||||
let output = (
|
||||
${output_components_joined}
|
||||
)
|
||||
return downstream.receive(output)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
% inner_description = 'ValueForKey' + ('' if arity == 1 else 's')
|
||||
var description: String { return "${inner_description}" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
% for i in range(arity):
|
||||
("${key_path_var(i, arity)}", ${key_path_var(i, arity)}),
|
||||
% end
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
% end
|
||||
@@ -5,51 +5,19 @@
|
||||
// Created by Sergej Jaskiewicz on 14.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
|
||||
: ConnectablePublisher
|
||||
where Upstream.Failure == SubjectType.Failure,
|
||||
Upstream.Output == SubjectType.Output
|
||||
{
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let createSubject: () -> SubjectType
|
||||
|
||||
private lazy var _subject: SubjectType = self.createSubject()
|
||||
|
||||
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
|
||||
self.upstream = upstream
|
||||
self.createSubject = createSubject
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubjectType.Failure == SubscriberType.Failure,
|
||||
SubjectType.Output == SubscriberType.Input
|
||||
{
|
||||
_subject.subscribe(subscriber)
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
|
||||
let subscriber = SubjectSubscriber(_subject)
|
||||
|
||||
upstream.subscribe(subscriber)
|
||||
|
||||
return AnyCancellable {
|
||||
subscriber.parent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Applies a closure to create a subject that delivers elements to subscribers.
|
||||
///
|
||||
/// Use a multicast publisher when you have multiple downstream subscribers, but you
|
||||
/// want upstream publishers to only process one `receive(_:)` call per event.
|
||||
/// In contrast with `multicast(subject:)`, this method produces a publisher that
|
||||
/// creates a separate Subject for each subscriber.
|
||||
///
|
||||
/// - Parameter createSubject: A closure to create a new Subject each time
|
||||
/// a subscriber attaches to the multicast publisher.
|
||||
public func multicast<SubjectType: Subject>(
|
||||
_ createSubject: @escaping () -> SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
@@ -58,6 +26,14 @@ extension Publisher {
|
||||
return Publishers.Multicast(upstream: self, createSubject: createSubject)
|
||||
}
|
||||
|
||||
/// Provides a subject to deliver elements to multiple subscribers.
|
||||
///
|
||||
/// Use a multicast publisher when you have multiple downstream subscribers, but you
|
||||
/// want upstream publishers to only process one `receive(_:)` call per event.
|
||||
/// In contrast with `multicast(_:)`, this method produces a publisher shares
|
||||
/// the provided Subject among all the downstream subscribers.
|
||||
///
|
||||
/// - Parameter subject: A subject to deliver elements to downstream subscribers.
|
||||
public func multicast<SubjectType: Subject>(
|
||||
subject: SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
@@ -66,3 +42,176 @@ extension Publisher {
|
||||
return multicast { subject }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that uses a subject to deliver elements to multiple subscribers.
|
||||
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
|
||||
: ConnectablePublisher
|
||||
where Upstream.Failure == SubjectType.Failure,
|
||||
Upstream.Output == SubjectType.Output
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure to create a new Subject each time a subscriber attaches
|
||||
/// to the multicast publisher.
|
||||
public let createSubject: () -> SubjectType
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var subject: SubjectType?
|
||||
|
||||
private var lazySubject: SubjectType {
|
||||
lock.lock()
|
||||
if let subject = subject {
|
||||
lock.unlock()
|
||||
return subject
|
||||
}
|
||||
|
||||
let subject = createSubject()
|
||||
self.subject = subject
|
||||
lock.unlock()
|
||||
return subject
|
||||
}
|
||||
|
||||
/// Creates a multicast publisher that applies a closure to create a subject
|
||||
/// that delivers elements to subscribers.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
/// - Parameter createSubject: A closure to create a new Subject each time
|
||||
/// a subscriber attaches to the multicast publisher.
|
||||
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
|
||||
self.upstream = upstream
|
||||
self.createSubject = createSubject
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where SubjectType.Failure == Downstream.Failure,
|
||||
SubjectType.Output == Downstream.Input
|
||||
{
|
||||
lazySubject.subscribe(Inner(parent: self, downstream: subscriber))
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
return upstream.subscribe(lazySubject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Multicast {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread safety
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private enum State {
|
||||
case ready(upstream: Upstream, downstream: Downstream)
|
||||
case subscribed(upstream: Upstream,
|
||||
downstream: Downstream,
|
||||
subjectSubscription: Subscription)
|
||||
case terminal
|
||||
}
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var state: State
|
||||
|
||||
fileprivate init(parent: Publishers.Multicast<Upstream, SubjectType>,
|
||||
downstream: Downstream) {
|
||||
state = .ready(upstream: parent.upstream, downstream: downstream)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
fileprivate var description: String { return "Multicast" }
|
||||
|
||||
fileprivate var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
fileprivate var playgroundDescription: Any { return description }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case let .ready(upstream, downstream) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .subscribed(upstream: upstream,
|
||||
downstream: downstream,
|
||||
subjectSubscription: subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, subjectSubscription) = state else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
lock.unlock()
|
||||
let newDemand = downstream.receive(input)
|
||||
if newDemand > 0 {
|
||||
subjectSubscription.request(newDemand)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, downstream, _) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subjectSubscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subjectSubscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(_, _, subjectSubscription) = state else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
state = .terminal
|
||||
lock.unlock()
|
||||
subjectSubscription.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,387 +0,0 @@
|
||||
//
|
||||
// Publishers.Once.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes an output to each subscriber exactly once then
|
||||
/// finishes, or fails immediately without producing any elements.
|
||||
///
|
||||
/// If `result` is `.success`, then `Once` waits until it receives a request for
|
||||
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
|
||||
/// sends the failure immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
|
||||
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
|
||||
/// value (unless it terminates with an error).
|
||||
public struct Once<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let result: Result<Output, Failure>
|
||||
|
||||
/// Creates a publisher that delivers the specified result.
|
||||
///
|
||||
/// If the result is `.success`, the `Once` publisher sends the specified output
|
||||
/// to all subscribers and finishes normally. If the result is `.failure`, then
|
||||
/// the publisher fails immediately with the specified error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ result: Result<Output, Failure>) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
/// Creates a publisher that sends the specified output to all subscribers and
|
||||
/// finishes normally.
|
||||
///
|
||||
/// - Parameter output: The output to deliver to each subscriber.
|
||||
public init(_ output: Output) {
|
||||
self.init(.success(output))
|
||||
}
|
||||
|
||||
/// Creates a publisher that immediately terminates upon subscription with
|
||||
/// the given failure.
|
||||
///
|
||||
/// - Parameter failure: The failure to send when terminating.
|
||||
public init(_ failure: Failure) {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubscriberType.Input == Output, SubscriberType.Failure == Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value):
|
||||
subscriber.receive(subscription: Inner(value: value,
|
||||
downstream: subscriber))
|
||||
case .failure(let failure):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Once" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once: Equatable where Output: Equatable, Failure: Equatable {}
|
||||
|
||||
extension Publishers.Once where Output: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Publishers.Once<Bool, Failure> {
|
||||
return Publishers.Once(result.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once where Output: Comparable {
|
||||
|
||||
public func min() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
return Publishers.Once(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
return Publishers.Once(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func collect() -> Publishers.Once<[Output], Failure> {
|
||||
return Publishers.Once(result.map { [$0] })
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output
|
||||
) -> Bool) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Once<Int, Failure> {
|
||||
return Publishers.Once(result.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return Publishers.Optional((try? result.get()).flatMap { count == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? nil : $0 })
|
||||
}
|
||||
|
||||
public func tryDrop(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? nil : $0 })
|
||||
}
|
||||
|
||||
public func first() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func last() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { isIncluded($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try isIncluded($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
|
||||
return Publishers.Empty()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Failure> {
|
||||
return Publishers.Once(result.map(transform))
|
||||
}
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(result.tryMap(transform))
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(result.map(transform))
|
||||
}
|
||||
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(result.tryMap(transform))
|
||||
}
|
||||
|
||||
public func mapError<TransformedFailure: Error>(
|
||||
_ transform: (Failure) -> TransformedFailure
|
||||
) -> Publishers.Once<Output, TransformedFailure> {
|
||||
return Publishers.Once(result.mapError(transform))
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return Publishers.Optional(result.map { index == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func output<RangeExpr: RangeExpression>(
|
||||
in range: RangeExpr
|
||||
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
let range = range.relative(to: 0..<Int.max)
|
||||
return Publishers.Optional(
|
||||
result.map { range.lowerBound == 0 ? $0 : nil }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually probably should be just this:
|
||||
// return Publishers.Optional(
|
||||
// result.map { range.contains(0) ? $0 : nil }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
// TODO: Seems broken in Apple's Combine (FB6168300)
|
||||
return Publishers.Optional(
|
||||
result.map { maxLength == 0 ? $0 : nil }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually should be the following:
|
||||
// return Publishers.Optional(
|
||||
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryPrefix(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Error> {
|
||||
return Publishers.Once(result.mapError { $0 })
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Publishers.Once<Output, Never> {
|
||||
return Publishers.Once(.success(result.unwrapOr(output)))
|
||||
}
|
||||
|
||||
public func replaceEmpty(with output: Output) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Failure> {
|
||||
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Error> {
|
||||
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Failure> {
|
||||
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once where Failure == Never {
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return Publishers.Once(result.success)
|
||||
}
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
//
|
||||
// Publishers.Optional.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes an optional value to each subscriber exactly once, if
|
||||
/// the optional has a value.
|
||||
///
|
||||
/// If `result` is `.success`, and the value is non-nil, then `Optional` waits until
|
||||
/// receiving a request for at least 1 value before sending the output. If `result` is
|
||||
/// `.failure`, then `Optional` sends the failure immediately upon subscription.
|
||||
/// If `result` is `.success` and the value is nil, then `Optional` sends `.finished`
|
||||
/// immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher can send an error.
|
||||
/// In contrast with `Once`, an `Optional` publisher can send zero values and finish
|
||||
/// normally, or send zero values and fail with an error.
|
||||
public struct Optional<Output, Failure: Error>: Publisher {
|
||||
// swiftlint:disable:previous syntactic_sugar
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let result: Result<Output?, Failure>
|
||||
|
||||
/// Creates a publisher to emit the optional value of a successful result, or fail
|
||||
/// with an error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ result: Result<Output?, Failure>) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
public init(_ output: Output?) {
|
||||
self.init(.success(output))
|
||||
}
|
||||
|
||||
public init(_ failure: Failure) {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value?):
|
||||
subscriber.receive(subscription: Inner(value: value,
|
||||
downstream: subscriber))
|
||||
case .success(nil):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .finished)
|
||||
case .failure(let failure):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Optional" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional: Equatable where Output: Equatable, Failure: Equatable {}
|
||||
|
||||
extension Publishers.Optional where Output: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Publishers.Optional<Bool, Failure> {
|
||||
return Publishers.Optional(result.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional where Output: Comparable {
|
||||
|
||||
public func min() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Bool, Failure> {
|
||||
return Publishers.Optional(result.map { $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func collect() -> Publishers.Optional<[Output], Failure> {
|
||||
return Publishers.Optional(result.map { $0.map { [$0] } })
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap(transform) })
|
||||
}
|
||||
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.flatMap(transform) })
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Bool, Failure> {
|
||||
return Publishers.Optional(result.map { $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Optional<Int, Failure> {
|
||||
return Publishers.Optional(result.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return Publishers.Optional(try? result.get().flatMap { count == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? nil : $0 } })
|
||||
}
|
||||
|
||||
public func tryDrop(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? nil : $0 } }
|
||||
)
|
||||
}
|
||||
|
||||
public func first() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func last() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { isIncluded($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try isIncluded($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
|
||||
return Publishers.Empty()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(result.map { $0.map(transform) })
|
||||
}
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.map(transform) })
|
||||
}
|
||||
|
||||
public func mapError<TransformedFailure: Error>(
|
||||
_ transform: (Failure) -> TransformedFailure
|
||||
) -> Publishers.Optional<Output, TransformedFailure> {
|
||||
return Publishers.Optional(result.mapError(transform))
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return Publishers.Optional(result.map { $0.flatMap { index == 0 ? $0 : nil } })
|
||||
}
|
||||
|
||||
public func output<RangeExpr: RangeExpression>(
|
||||
in range: RangeExpr
|
||||
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
let range = range.relative(to: 0..<Int.max)
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { range.lowerBound == 0 ? $0 : nil } }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually probably should be just this:
|
||||
// return Publishers.Optional(
|
||||
// result.map { $0.flatMap { range.contains(0) ? $0 : nil } }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
// TODO: Seems broken in Apple's Combine (FB6168300)
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { maxLength == 0 ? $0 : nil } }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually should be the following:
|
||||
// return Publishers.Optional(
|
||||
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryPrefix(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Optional<Accumulator, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.map { nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.Optional<Accumulator, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.map { nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.mapError { $0 })
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(.success(result.unwrapOr(output)))
|
||||
}
|
||||
|
||||
public func replaceEmpty(
|
||||
with output: Output
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional where Failure == Never {
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.success)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that prints log messages for all publishing events, optionally
|
||||
@@ -43,11 +45,11 @@ extension Publishers {
|
||||
self.stream = stream
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, prefix: prefix, stream: stream)
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,99 +67,97 @@ extension Publisher {
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<Downstream: Subscriber>: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
typealias Input = Downstream.Input
|
||||
typealias Failure = Downstream.Failure
|
||||
extension Publishers.Print {
|
||||
private final class Inner<Downstream: Subscriber>: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
typealias Input = Downstream.Input
|
||||
typealias Failure = Downstream.Failure
|
||||
|
||||
private var _downstream: Downstream
|
||||
private let _prefix: String
|
||||
private var _stream: TextOutputStream
|
||||
private var _upstreamSubscription: Subscription?
|
||||
private let _printerLock = Lock(recursive: false)
|
||||
/// A concrete type wrapper around an abstract stream.
|
||||
private struct PrintTarget: TextOutputStream {
|
||||
|
||||
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
|
||||
_downstream = downstream
|
||||
_prefix = prefix
|
||||
_stream = stream ?? StdoutStream()
|
||||
}
|
||||
var stream: TextOutputStream
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
_log("receive subscription", value: subscription)
|
||||
_upstreamSubscription = subscription
|
||||
_downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
_log("receive value", value: input)
|
||||
let demand = _downstream.receive(input)
|
||||
_logDemand(demand, synchronous: true)
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
_log("receive finished")
|
||||
case .failure(let error):
|
||||
_log("receive error", value: error)
|
||||
mutating func write(_ string: String) {
|
||||
stream.write(string)
|
||||
}
|
||||
}
|
||||
_downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
_logDemand(demand, synchronous: false)
|
||||
_upstreamSubscription?.request(demand)
|
||||
}
|
||||
private var downstream: Downstream
|
||||
private let prefix: String
|
||||
private var stream: PrintTarget?
|
||||
private var subscription: Subscription?
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
func cancel() {
|
||||
_log("receive cancel")
|
||||
_upstreamSubscription?.cancel()
|
||||
_upstreamSubscription = nil
|
||||
}
|
||||
|
||||
var description: String { return "Print" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
private func _log(_ description: String,
|
||||
value: Any? = nil,
|
||||
additionalInfo: String = "") {
|
||||
_printerLock.do {
|
||||
if !_prefix.isEmpty {
|
||||
_stream.write(_prefix)
|
||||
_stream.write(": ")
|
||||
}
|
||||
_stream.write(description)
|
||||
if let value = value {
|
||||
_stream.write(": (")
|
||||
_stream.write(String(describing: value))
|
||||
_stream.write(")")
|
||||
}
|
||||
if !additionalInfo.isEmpty {
|
||||
_stream.write(" (")
|
||||
_stream.write(additionalInfo)
|
||||
_stream.write(")")
|
||||
}
|
||||
_stream.write("\n")
|
||||
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
|
||||
self.downstream = downstream
|
||||
self.prefix = prefix.isEmpty ? "" : "\(prefix): "
|
||||
self.stream = stream.map(PrintTarget.init)
|
||||
}
|
||||
}
|
||||
|
||||
private func _logDemand(_ demand: Subscribers.Demand, synchronous: Bool) {
|
||||
let synchronouslyStr = synchronous ? "synchronous" : ""
|
||||
if let max = demand.max {
|
||||
_log("request max", value: max, additionalInfo: synchronouslyStr)
|
||||
} else {
|
||||
_log("request unlimited", additionalInfo: synchronouslyStr)
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
log("\(prefix)receive subscription: (\(subscription))")
|
||||
lock.do {
|
||||
self.subscription = subscription
|
||||
}
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
log("\(prefix)receive value: (\(input))")
|
||||
let demand = downstream.receive(input)
|
||||
|
||||
if let max = demand.max {
|
||||
log("\(prefix)request max: (\(max)) (synchronous)")
|
||||
} else {
|
||||
log("\(prefix)request unlimited (synchronous)")
|
||||
}
|
||||
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
log("\(prefix)receive finished")
|
||||
case .failure(let error):
|
||||
log("\(prefix)receive error: (\(error))")
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let max = demand.max {
|
||||
log("\(prefix)request max: (\(max))")
|
||||
} else {
|
||||
log("\(prefix)request unlimited")
|
||||
}
|
||||
subscription?.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
log("\(prefix)receive cancel")
|
||||
subscription?.cancel()
|
||||
subscription = nil
|
||||
}
|
||||
|
||||
var description: String { return "Print" }
|
||||
|
||||
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
|
||||
|
||||
private func log(_ text: String) {
|
||||
if var stream = stream {
|
||||
Swift.print(text, to: &stream)
|
||||
} else {
|
||||
Swift.print(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct StdoutStream: TextOutputStream {
|
||||
mutating func write(_ string: String) {
|
||||
print(string, terminator: "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// Publishers.Reduce.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 09.10.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Applies a closure that accumulates each element of a stream and publishes
|
||||
/// a final result upon completion.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The value the closure receives the first time it is called.
|
||||
/// - nextPartialResult: A closure that takes the previously-accumulated value and
|
||||
/// the next element from the upstream publisher to produce a new value.
|
||||
/// - Returns: A publisher that applies the closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Reduce<Self, Accumulator> {
|
||||
return .init(upstream: self,
|
||||
initial: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
|
||||
/// Applies an error-throwing closure that accumulates each element of a stream and
|
||||
/// publishes a final result upon completion.
|
||||
///
|
||||
/// If the closure throws an error, the publisher fails, passing the error
|
||||
/// to its subscriber.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The value the closure receives the first time it is called.
|
||||
/// - nextPartialResult: An error-throwing closure that takes
|
||||
/// the previously-accumulated value and the next element from the upstream
|
||||
/// publisher to produce a new value.
|
||||
/// - Returns: A publisher that applies the closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.TryReduce<Self, Accumulator> {
|
||||
return .init(upstream: self,
|
||||
initial: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that applies a closure to all received elements and produces
|
||||
/// an accumulated value when the upstream publisher finishes.
|
||||
public struct Reduce<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The initial value provided on the first invocation of the closure.
|
||||
public let initial: Output
|
||||
|
||||
/// A closure that takes the previously-accumulated value and the next element
|
||||
/// from the upstream publisher to produce a new value.
|
||||
public let nextPartialResult: (Output, Upstream.Output) -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
initial: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) -> Output) {
|
||||
self.upstream = upstream
|
||||
self.initial = initial
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
initial: initial,
|
||||
reduce: nextPartialResult)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that applies an error-throwing closure to all received elements and
|
||||
/// produces an accumulated value when the upstream publisher finishes.
|
||||
public struct TryReduce<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The initial value provided on the first invocation of the closure.
|
||||
public let initial: Output
|
||||
|
||||
/// An error-throwing closure that takes the previously-accumulated value and
|
||||
/// the next element from the upstream to produce a new value.
|
||||
///
|
||||
/// If this closure throws an error, the publisher fails and passes the error
|
||||
/// to its subscriber.
|
||||
public let nextPartialResult: (Output, Upstream.Output) throws -> Output
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
initial: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.initial = initial
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
initial: initial,
|
||||
reduce: nextPartialResult)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Reduce {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Output, Upstream.Output) -> Output>
|
||||
where Downstream.Input == Output, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
result = reduce(result!, newValue)
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String { return "Reduce" }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryReduce {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Output,
|
||||
Upstream.Failure,
|
||||
(Output, Upstream.Output) throws -> Output>
|
||||
where Downstream.Input == Output, Downstream.Failure == Error
|
||||
{
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
result = try reduce(result!, newValue)
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String { return "TryReduce" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// Publishers.ReplaceError.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Bogdan Vlad on 8/29/19.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publisher {
|
||||
/// Replaces any errors in the stream with the provided element.
|
||||
///
|
||||
/// If the upstream publisher fails with an error, this publisher emits the provided
|
||||
/// element, then finishes normally.
|
||||
/// - Parameter output: An element to emit when the upstream publisher fails.
|
||||
/// - Returns: A publisher that replaces an error from the upstream publisher with
|
||||
/// the provided output element.
|
||||
public func replaceError(with output: Output) -> Publishers.ReplaceError<Self> {
|
||||
return .init(upstream: self, output: output)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
/// A publisher that replaces any errors in the stream with a provided element.
|
||||
public struct ReplaceError<Upstream: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The element with which to replace errors from the upstream publisher.
|
||||
public let output: Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream,
|
||||
output: Output) {
|
||||
self.upstream = upstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber, output: output))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.ReplaceError: Equatable
|
||||
where Upstream: Equatable, Upstream.Output: Equatable
|
||||
{}
|
||||
|
||||
extension Publishers.ReplaceError {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let output: Upstream.Output
|
||||
private let downstream: Downstream
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
private var terminated = false
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
fileprivate init(downstream: Downstream, output: Upstream.Output) {
|
||||
self.downstream = downstream
|
||||
self.output = output
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.lock()
|
||||
guard case .subscribed = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
pendingDemand -= 1
|
||||
lock.unlock()
|
||||
let demand = downstream.receive(input)
|
||||
guard demand > 0 else {
|
||||
return .none
|
||||
}
|
||||
lock.lock()
|
||||
pendingDemand += demand
|
||||
lock.unlock()
|
||||
return demand
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure:
|
||||
lock.lock()
|
||||
// If there was no demand from downstream,
|
||||
// ReplaceError does not forward the value that
|
||||
// replaces the error until it is requested.
|
||||
guard pendingDemand > 0 else {
|
||||
terminated = true
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
lock.lock()
|
||||
if terminated {
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
return
|
||||
}
|
||||
pendingDemand += demand
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "ReplaceError" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Publishers.ReplaceNil.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces nil elements in the stream with the proviced element.
|
||||
///
|
||||
/// - Parameter output: The element to use when replacing `nil`.
|
||||
/// - Returns: A publisher that replaces `nil` elements from
|
||||
/// the upstream publisher with the provided element.
|
||||
public func replaceNil<ElementOfResult>(
|
||||
with output: ElementOfResult
|
||||
) -> Publishers.Map<Self, ElementOfResult>
|
||||
where Output == ElementOfResult?
|
||||
{
|
||||
return Publishers.Map(upstream: self) { $0 ?? output }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
//
|
||||
// Publishers.Scan.swift
|
||||
//
|
||||
// Created by Eric Patey on 26.08.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Transforms elements from the upstream publisher by providing the current element
|
||||
/// to a closure along with the last value returned by the closure.
|
||||
///
|
||||
/// let pub = (0...5)
|
||||
/// .publisher
|
||||
/// .scan(0, { return $0 + $1 })
|
||||
/// .sink(receiveValue: { print ("\($0)", terminator: " ") })
|
||||
/// // Prints "0 1 3 6 10 15 ".
|
||||
///
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initialResult: The previous result returned by the `nextPartialResult`
|
||||
/// closure.
|
||||
/// - nextPartialResult: A closure that takes as its arguments the previous value
|
||||
/// returned by the closure and the next element emitted from the upstream
|
||||
/// publisher.
|
||||
/// - Returns: A publisher that transforms elements by applying a closure that
|
||||
/// receives its previous return value and the next element from the upstream
|
||||
/// publisher.
|
||||
public func scan<Result>(
|
||||
_ initialResult: Result,
|
||||
_ nextPartialResult: @escaping (Result, Output) -> Result
|
||||
) -> Publishers.Scan<Self, Result> {
|
||||
return .init(upstream: self,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
|
||||
/// Transforms elements from the upstream publisher by providing the current element
|
||||
/// to an error-throwing closure along with the last value returned by the closure.
|
||||
///
|
||||
/// If the closure throws an error, the publisher fails with the error.
|
||||
/// - Parameters:
|
||||
/// - initialResult: The previous result returned by the `nextPartialResult`
|
||||
/// closure.
|
||||
/// - nextPartialResult: An error-throwing closure that takes as its arguments the
|
||||
/// previous value returned by the closure and the next element emitted from the
|
||||
/// upstream publisher.
|
||||
/// - Returns: A publisher that transforms elements by applying a closure that
|
||||
/// receives its previous return value and the next element from the upstream
|
||||
/// publisher.
|
||||
public func tryScan<Result>(
|
||||
_ initialResult: Result,
|
||||
_ nextPartialResult: @escaping (Result, Output) throws -> Result
|
||||
) -> Publishers.TryScan<Self, Result> {
|
||||
return .init(upstream: self,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public struct Scan<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let initialResult: Output
|
||||
|
||||
public let nextPartialResult: (Output, Upstream.Output) -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
initialResult: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) -> Output) {
|
||||
self.upstream = upstream
|
||||
self.initialResult = initialResult
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult))
|
||||
}
|
||||
}
|
||||
|
||||
public struct TryScan<Upstream: Publisher, Output>: Publisher {
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public let initialResult: Output
|
||||
|
||||
public let nextPartialResult: (Output, Upstream.Output) throws -> Output
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
initialResult: Output,
|
||||
nextPartialResult: @escaping (Output, Upstream.Output) throws -> Output
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.initialResult = initialResult
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber,
|
||||
initialResult: initialResult,
|
||||
nextPartialResult: nextPartialResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Scan {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let nextPartialResult: (Downstream.Input, Input) -> Downstream.Input
|
||||
|
||||
private var result: Downstream.Input
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
initialResult: Downstream.Input,
|
||||
nextPartialResult: @escaping (Downstream.Input, Input) -> Downstream.Input
|
||||
)
|
||||
{
|
||||
self.downstream = downstream
|
||||
self.result = initialResult
|
||||
self.nextPartialResult = nextPartialResult
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
result = nextPartialResult(result, input)
|
||||
return downstream.receive(result)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Scan" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("result", result)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryScan {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Failure == Error
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
private let downstream: Downstream
|
||||
|
||||
private let nextPartialResult:
|
||||
(Downstream.Input, Input) throws -> Downstream.Input
|
||||
|
||||
private var result: Downstream.Input
|
||||
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
private let lock = UnfairLock.allocate()
|
||||
|
||||
private var finished = false
|
||||
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
initialResult: Downstream.Input,
|
||||
nextPartialResult:
|
||||
@escaping (Downstream.Input, Input) throws -> Downstream.Input
|
||||
) {
|
||||
self.downstream = downstream
|
||||
self.nextPartialResult = nextPartialResult
|
||||
self.result = initialResult
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.lock()
|
||||
guard case .awaitingSubscription = status else {
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
return
|
||||
}
|
||||
status = .subscribed(subscription)
|
||||
lock.unlock()
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
do {
|
||||
result = try nextPartialResult(result, input)
|
||||
return downstream.receive(result)
|
||||
} catch {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return .none
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
downstream.receive(completion: .failure(error))
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
// Combine doesn't use locking in this method!
|
||||
guard case .subscribed = status else {
|
||||
return
|
||||
}
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
subscription.request(demand)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.lock()
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
status = .terminal
|
||||
lock.unlock()
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "TryScan" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let children: [Mirror.Child] = [
|
||||
("downstream", downstream),
|
||||
("status", status),
|
||||
("result", result)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Sergej Jaskiewicz on 19.06.2019.
|
||||
//
|
||||
|
||||
import COpenCombineHelpers
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes a given sequence of elements.
|
||||
@@ -25,11 +27,13 @@ extension Publishers {
|
||||
self.sequence = sequence
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure,
|
||||
Elements.Element == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure,
|
||||
Elements.Element == Downstream.Input
|
||||
{
|
||||
if let inner = Inner(downstream: subscriber, sequence: sequence) {
|
||||
var iterator = sequence.makeIterator()
|
||||
if iterator.next() != nil {
|
||||
let inner = Inner(downstream: subscriber, sequence: sequence)
|
||||
subscriber.receive(subscription: inner)
|
||||
} else {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
@@ -44,87 +48,135 @@ extension Publishers.Sequence {
|
||||
private final class Inner<Downstream: Subscriber, Elements: Sequence, Failure>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Elements.Element,
|
||||
Downstream.Failure == Failure
|
||||
{
|
||||
// NOTE: This class has been audited for thread-safety
|
||||
|
||||
typealias Iterator = Elements.Iterator
|
||||
typealias Element = Elements.Element
|
||||
|
||||
private var _downstream: Downstream?
|
||||
private var _sequence: Elements?
|
||||
private var _iterator: Iterator?
|
||||
private var _nextValue: Element?
|
||||
private var sequence: Elements?
|
||||
private var downstream: Downstream?
|
||||
private var iterator: Iterator
|
||||
private var next: Element?
|
||||
private var pendingDemand = Subscribers.Demand.none
|
||||
private var recursion = false
|
||||
private var lock = UnfairLock.allocate()
|
||||
|
||||
init?(downstream: Downstream, sequence: Elements) {
|
||||
fileprivate init(downstream: Downstream, sequence: Elements) {
|
||||
self.sequence = sequence
|
||||
self.downstream = downstream
|
||||
self.iterator = sequence.makeIterator()
|
||||
next = iterator.next()
|
||||
}
|
||||
|
||||
// Early exit if the sequence is empty
|
||||
var iterator = sequence.makeIterator()
|
||||
guard iterator.next() != nil else { return nil }
|
||||
|
||||
_downstream = downstream
|
||||
_sequence = sequence
|
||||
_iterator = sequence.makeIterator()
|
||||
_nextValue = iterator.next()
|
||||
deinit {
|
||||
lock.deallocate()
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return _sequence.map(String.init(describing:)) ?? "Sequence"
|
||||
return sequence.map(String.init(describing:)) ?? "Sequence"
|
||||
}
|
||||
|
||||
var customMirror: Mirror {
|
||||
let children: CollectionOfOne<(label: String?, value: Any)> =
|
||||
CollectionOfOne(("sequence", _sequence ?? [Element]()))
|
||||
let children =
|
||||
CollectionOfOne<Mirror.Child>(("sequence", sequence ?? [Element]()))
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.lock()
|
||||
guard downstream != nil else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
pendingDemand += demand
|
||||
if recursion {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
guard let downstream = _downstream else { return }
|
||||
while let downstream = self.downstream, pendingDemand > 0 {
|
||||
if let current = self.next {
|
||||
pendingDemand -= 1
|
||||
|
||||
var demand = demand
|
||||
|
||||
while demand > 0 {
|
||||
if let nextValue = _nextValue {
|
||||
demand += downstream.receive(nextValue)
|
||||
demand -= 1
|
||||
// Combine calls next() while the lock is held.
|
||||
// It is possible to engineer a custom Sequence that would cause
|
||||
// a dedlock here, but it would be something insane.
|
||||
let next = iterator.next()
|
||||
recursion = true
|
||||
lock.unlock()
|
||||
let additionalDemand = downstream.receive(current)
|
||||
lock.lock()
|
||||
recursion = false
|
||||
pendingDemand += additionalDemand
|
||||
self.next = next
|
||||
}
|
||||
|
||||
_nextValue = _iterator?.next()
|
||||
|
||||
if _nextValue == nil {
|
||||
_downstream?.receive(completion: .finished)
|
||||
cancel()
|
||||
break
|
||||
if next == nil {
|
||||
self.downstream = nil
|
||||
self.sequence = nil
|
||||
lock.unlock()
|
||||
downstream.receive(completion: .finished)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
_iterator = nil
|
||||
_sequence = nil
|
||||
lock.lock()
|
||||
downstream = nil
|
||||
sequence = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence: Equatable where Elements: Equatable {}
|
||||
|
||||
extension Publishers.Sequence where Failure == Never {
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.min(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.max(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.first(where: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.allSatisfy(predicate))
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try sequence.allSatisfy(predicate) })
|
||||
}
|
||||
|
||||
public func collect() -> Publishers.Once<[Elements.Element], Failure> {
|
||||
public func collect() -> Result<[Elements.Element], Failure>.OCombine.Publisher {
|
||||
return .init(Array(sequence))
|
||||
}
|
||||
|
||||
@@ -134,39 +186,15 @@ extension Publishers.Sequence {
|
||||
return .init(sequence: sequence.compactMap(transform))
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.min(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.min(by: areInIncreasingOrder) })
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.max(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.max(by: areInIncreasingOrder) })
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.contains(where: predicate))
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try sequence.contains(where: predicate) })
|
||||
}
|
||||
|
||||
@@ -182,26 +210,14 @@ extension Publishers.Sequence {
|
||||
return .init(sequence: sequence.dropFirst(count))
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.first(where: predicate))
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.first(where: predicate) })
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Elements.Element) -> Bool
|
||||
) -> Publishers.Sequence<[Elements.Element], Failure> {
|
||||
return .init(sequence: sequence.filter(isIncluded))
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Elements.Element, Failure> {
|
||||
return .init(completeImmediately: true)
|
||||
public func ignoreOutput() -> Empty<Elements.Element, Failure> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
@@ -225,7 +241,7 @@ extension Publishers.Sequence {
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Elements.Element) -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Failure> {
|
||||
) -> Result<Accumulator, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.reduce(initialResult, nextPartialResult))
|
||||
}
|
||||
|
||||
@@ -233,7 +249,7 @@ extension Publishers.Sequence {
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult:
|
||||
@escaping (Accumulator, Elements.Element) throws -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Error> {
|
||||
) -> Result<Accumulator, Error>.OCombine.Publisher {
|
||||
return .init(Result { try sequence.reduce(initialResult, nextPartialResult) })
|
||||
}
|
||||
|
||||
@@ -276,38 +292,43 @@ extension Publishers.Sequence where Elements.Element: Equatable {
|
||||
return .init(sequence: result)
|
||||
}
|
||||
|
||||
public func contains(_ output: Elements.Element) -> Publishers.Once<Bool, Failure> {
|
||||
public func contains(
|
||||
_ output: Elements.Element
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.contains(output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements.Element: Comparable {
|
||||
extension Publishers.Sequence where Failure == Never, Elements.Element: Comparable {
|
||||
|
||||
public func min() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
public func min() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.min())
|
||||
}
|
||||
|
||||
public func max() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
public func max() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.max())
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: Collection, Failure == Never {
|
||||
|
||||
public func first() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.first)
|
||||
}
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: Collection {
|
||||
|
||||
public func first() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.first)
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Once<Int, Failure> {
|
||||
public func count() -> Result<Int, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.count)
|
||||
}
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
|
||||
public func output(
|
||||
in range: Range<Elements.Index>
|
||||
) -> Publishers.Sequence<[Elements.Element], Failure> {
|
||||
@@ -315,40 +336,41 @@ extension Publishers.Sequence where Elements: Collection {
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: BidirectionalCollection {
|
||||
extension Publishers.Sequence where Elements: BidirectionalCollection, Failure == Never {
|
||||
|
||||
public func last() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
public func last() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.last)
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.last(where: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.last(where: predicate) })
|
||||
extension Publishers.Sequence where Elements: RandomAccessCollection, Failure == Never {
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
|
||||
public func count() -> Just<Int> {
|
||||
return .init(sequence.count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: RandomAccessCollection {
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
|
||||
public func output(
|
||||
in range: Range<Elements.Index>
|
||||
) -> Publishers.Sequence<[Elements.Element], Failure> {
|
||||
return .init(sequence: Array(sequence[range]))
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Optional<Int, Failure> {
|
||||
public func count() -> Result<Int, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.count)
|
||||
}
|
||||
}
|
||||
@@ -408,7 +430,7 @@ extension Publishers.Sequence where Elements: RangeReplaceableCollection {
|
||||
|
||||
extension Sequence {
|
||||
|
||||
public func publisher() -> Publishers.Sequence<Self, Never> {
|
||||
return Publishers.Sequence(sequence: self)
|
||||
public var publisher: Publishers.Sequence<Self, Never> {
|
||||
return .init(sequence: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Publishers.SetFailureType.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.07.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that appears to send a specified failure type.
|
||||
///
|
||||
/// The publisher cannot actually fail with the specified type and instead
|
||||
/// just finishes normally. Use this publisher type when you need to match
|
||||
/// the error types for two mismatched publishers.
|
||||
public struct SetFailureType<Upstream: Publisher, Failure: Error>: Publisher
|
||||
where Upstream.Failure == Never
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Creates a publisher that appears to send a specified failure type.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
upstream.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
|
||||
public func setFailureType<NewFailure: Error>(
|
||||
to failure: NewFailure.Type
|
||||
) -> Publishers.SetFailureType<Upstream, NewFailure> {
|
||||
return .init(upstream: upstream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SetFailureType: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Changes the failure type declared by the upstream publisher.
|
||||
///
|
||||
/// The publisher returned by this method cannot actually fail
|
||||
/// with the specified type and instead just finishes normally. Instead, you use
|
||||
/// this method when you need to match the error types of two mismatched publishers.
|
||||
///
|
||||
/// - Parameter failureType: The `Failure` type presented by this publisher.
|
||||
/// - Returns: A publisher that appears to send the specified failure type.
|
||||
public func setFailureType<NewFailure: Error>(
|
||||
to failureType: NewFailure.Type
|
||||
) -> Publishers.SetFailureType<Self, NewFailure> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SetFailureType {
|
||||
private struct Inner<Downstream: Subscriber>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Upstream.Output == Downstream.Input, Failure == Downstream.Failure
|
||||
{
|
||||
private let downstream: Downstream
|
||||
let combineIdentifier = CombineIdentifier()
|
||||
|
||||
fileprivate init(downstream: Downstream) {
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Never>) {
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
var description: String { return "SetFailureType" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, children: EmptyCollection())
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Publishers.Share
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18/09/2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher as a class instance.
|
||||
///
|
||||
/// The downstream subscriber receieves elements and completion states unchanged from
|
||||
/// the upstream publisher. Use this operator when you want to use
|
||||
/// reference semantics, such as storing a publisher instance in a property.
|
||||
///
|
||||
/// - Returns: A class instance that republishes its upstream publisher.
|
||||
public func share() -> Publishers.Share<Self> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher implemented as a class, which otherwise behaves like its upstream
|
||||
/// publisher.
|
||||
public final class Share<Upstream: Publisher>: Publisher, Equatable {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
private typealias MulticastSubject = PassthroughSubject<Output, Failure>
|
||||
|
||||
private let inner: Autoconnect<Multicast<Upstream, MulticastSubject>>
|
||||
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.inner = upstream.multicast(subject: .init()).autoconnect()
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Output, Downstream.Failure == Failure
|
||||
{
|
||||
inner.subscribe(subscriber)
|
||||
}
|
||||
|
||||
public static func == (lhs: Share, rhs: Share) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
//
|
||||
// Result.Publisher.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Result {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Combine extends `Result` with a nested type `Publisher`.
|
||||
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
|
||||
/// e. g. when importing Foundation), you will not be able
|
||||
/// to write `Result<Int, Error>.Publisher`,
|
||||
/// because Swift is unable to understand which `Publisher` you're referring to.
|
||||
///
|
||||
/// So you have to write `Result<Int, Error>.OCombine.Publisher`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine {
|
||||
|
||||
fileprivate let result: Result
|
||||
|
||||
fileprivate init(_ result: Result) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
public var publisher: Publisher {
|
||||
return Publisher(result)
|
||||
}
|
||||
|
||||
/// A publisher that publishes an output to each subscriber exactly once then
|
||||
/// finishes, or fails immediately without producing any elements.
|
||||
///
|
||||
/// If `result` is `.success`, then `Once` waits until it receives a request for
|
||||
/// at least 1 value before sending the output. If `result` is `.failure`,
|
||||
/// then `Once` sends the failure immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, a `Once` publisher can terminate with an error
|
||||
/// instead of sending a value. In contrast with `Optional`, a `Once` publisher
|
||||
/// always sends one value (unless it terminates with an error).
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
public typealias Output = Success
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let result: Result
|
||||
|
||||
/// Creates a publisher that delivers the specified result.
|
||||
///
|
||||
/// If the result is `.success`, the `Once` publisher sends the specified
|
||||
/// output to all subscribers and finishes normally. If the result is
|
||||
/// `.failure`, then the publisher fails immediately with the specified
|
||||
/// error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ result: Result) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
/// Creates a publisher that sends the specified output to all subscribers and
|
||||
/// finishes normally.
|
||||
///
|
||||
/// - Parameter output: The output to deliver to each subscriber.
|
||||
public init(_ output: Output) {
|
||||
self.init(.success(output))
|
||||
}
|
||||
|
||||
/// Creates a publisher that immediately terminates upon subscription with
|
||||
/// the given failure.
|
||||
///
|
||||
/// - Parameter failure: The failure to send when terminating.
|
||||
public init(_ failure: Failure) {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Input == Success, Downstream.Failure == Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value):
|
||||
subscriber.receive(subscription: Inner(value: value,
|
||||
downstream: subscriber))
|
||||
case .failure(let failure):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var ocombine: OCombine {
|
||||
return OCombine(self)
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that publishes an output to each subscriber exactly once then
|
||||
/// finishes, or fails immediately without producing any elements.
|
||||
///
|
||||
/// If `result` is `.success`, then `Once` waits until it receives a request for
|
||||
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
|
||||
/// sends the failure immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
|
||||
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
|
||||
/// value (unless it terminates with an error).
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
|
||||
public var publisher: Publisher {
|
||||
return Publisher(self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension Result.OCombine {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
where Downstream.Input == Success, Downstream.Failure == Failure
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
private var downstream: Downstream?
|
||||
private let output: Success
|
||||
|
||||
init(value: Success, downstream: Downstream) {
|
||||
self.output = value
|
||||
self.downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
demand.assertNonZero()
|
||||
guard let downstream = self.downstream else { return }
|
||||
self.downstream = nil
|
||||
_ = downstream.receive(output)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Once" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
|
||||
}
|
||||
|
||||
var playgroundDescription: Any { return description }
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher: Equatable
|
||||
where Output: Equatable, Failure: Equatable
|
||||
{
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher where Output: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher where Output: Comparable {
|
||||
|
||||
public func min() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func collect() -> Result<[Output], Failure>.OCombine.Publisher {
|
||||
return .init(result.map { [$0] })
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output
|
||||
) -> Bool) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
|
||||
}
|
||||
|
||||
public func count() -> Result<Int, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func first() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Empty<Output, Failure> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
|
||||
return .init(result.map(transform))
|
||||
}
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap(transform))
|
||||
}
|
||||
|
||||
public func mapError<TransformedFailure: Error>(
|
||||
_ transform: (Failure) -> TransformedFailure
|
||||
) -> Result<Output, TransformedFailure>.OCombine.Publisher {
|
||||
return .init(result.mapError(transform))
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { _ = try predicate($0, $0); return $0 })
|
||||
}
|
||||
|
||||
public func replaceError(
|
||||
with output: Output
|
||||
) -> Result<Output, Never>.OCombine.Publisher {
|
||||
return .init(.success(result.unwrapOr(output)))
|
||||
}
|
||||
|
||||
public func replaceEmpty(
|
||||
with output: Output
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Result<Accumulator, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Result<Accumulator, Error>.OCombine.Publisher{
|
||||
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher where Failure == Never {
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return .init(result.success)
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,6 @@ internal func catching<Input, Output, Failure: Error>(
|
||||
/// an error but returns `Result`.
|
||||
internal func catching<Input, Output>(
|
||||
_ transform: @escaping (Input) throws -> Output
|
||||
) -> (Input) -> Result<Output, Error> {
|
||||
) -> (Input) -> Result<Output, Error> {
|
||||
return { input in Result { try transform(input) } }
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ public protocol Subject: AnyObject, Publisher {
|
||||
/// - Parameter completion: A `Completion` instance which indicates whether publishing
|
||||
/// has finished normally or failed with an error.
|
||||
func send(completion: Subscribers.Completion<Failure>)
|
||||
|
||||
/// Provides this Subject an opportunity to establish demand for any new upstream
|
||||
/// subscriptions (say, via `Publisher.subscribe<S: Subject>(_: Subject)`)
|
||||
func send(subscription: Subscription)
|
||||
}
|
||||
|
||||
extension Subject where Output == Void {
|
||||
|
||||
@@ -45,3 +45,18 @@ extension Subscriber where Input == Void {
|
||||
return receive(())
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: Subscriber {
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
self?.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Wrapped.Input) -> Subscribers.Demand {
|
||||
return self?.receive(input) ?? .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Wrapped.Failure>) {
|
||||
self?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,24 @@ extension Subscribers {
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
public private(set) var object: Root?
|
||||
|
||||
public let keyPath: ReferenceWritableKeyPath<Root, Input>
|
||||
|
||||
private var _upstreamSubscription: Subscription?
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
public var description: String { return "Assign \(Root.self)." }
|
||||
|
||||
public var customMirror: Mirror {
|
||||
let children: [(label: String?, value: Any)] = [
|
||||
(label: "object", value: object as Any),
|
||||
(label: "keyPath", value: keyPath),
|
||||
(label: "upstreamSubscription", value: _upstreamSubscription as Any)
|
||||
let children: [Mirror.Child] = [
|
||||
("object", object as Any),
|
||||
("keyPath", keyPath),
|
||||
("status", status as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
@@ -40,17 +43,21 @@ extension Subscribers {
|
||||
}
|
||||
|
||||
public func receive(subscription: Subscription) {
|
||||
if _upstreamSubscription == nil {
|
||||
_upstreamSubscription = subscription
|
||||
subscription.request(.unlimited)
|
||||
} else {
|
||||
switch status {
|
||||
case .subscribed, .terminal:
|
||||
subscription.cancel()
|
||||
case .awaitingSubscription:
|
||||
status = .subscribed(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
public func receive(_ value: Input) -> Subscribers.Demand {
|
||||
if _upstreamSubscription != nil {
|
||||
switch status {
|
||||
case .subscribed:
|
||||
object?[keyPath: keyPath] = value
|
||||
case .awaitingSubscription, .terminal:
|
||||
break
|
||||
}
|
||||
return .none
|
||||
}
|
||||
@@ -60,8 +67,11 @@ extension Subscribers {
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
_upstreamSubscription?.cancel()
|
||||
_upstreamSubscription = nil
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
return
|
||||
}
|
||||
subscription.cancel()
|
||||
status = .terminal
|
||||
object = nil
|
||||
}
|
||||
}
|
||||
@@ -69,13 +79,14 @@ extension Subscribers {
|
||||
|
||||
extension Publisher where Self.Failure == Never {
|
||||
|
||||
/// Assigns the value of a KVO-compliant property from a publisher.
|
||||
/// Assigns each element from a Publisher to a property on an object.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath: The key path of the property to assign.
|
||||
/// - object: The object on which to assign the value.
|
||||
/// - Returns: A cancellable instance; used when you end KVO-based assignment of
|
||||
/// the key path’s value.
|
||||
/// - Returns: A cancellable instance; used when you end assignment
|
||||
/// of the received value. Deallocation of the result will tear down
|
||||
/// the subscription stream.
|
||||
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Output>,
|
||||
on object: Root) -> AnyCancellable {
|
||||
let subscriber = Subscribers.Assign(object: object, keyPath: keyPath)
|
||||
|
||||
@@ -24,23 +24,14 @@ extension Subscribers.Completion: Equatable where Failure: Equatable {}
|
||||
|
||||
extension Subscribers.Completion: Hashable where Failure: Hashable {}
|
||||
|
||||
extension Subscribers.Completion: Codable where Failure: Decodable, Failure: Encodable {
|
||||
|
||||
extension Subscribers.Completion {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case success = "success"
|
||||
case error = "error"
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let success = try container.decode(Bool.self, forKey: .success)
|
||||
if success {
|
||||
self = .finished
|
||||
} else {
|
||||
let error = try container.decode(Failure.self, forKey: .error)
|
||||
self = .failure(error)
|
||||
}
|
||||
}
|
||||
extension Subscribers.Completion: Encodable where Failure: Encodable {
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
@@ -54,6 +45,19 @@ extension Subscribers.Completion: Codable where Failure: Decodable, Failure: Enc
|
||||
}
|
||||
}
|
||||
|
||||
extension Subscribers.Completion: Decodable where Failure: Decodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let success = try container.decode(Bool.self, forKey: .success)
|
||||
if success {
|
||||
self = .finished
|
||||
} else {
|
||||
let error = try container.decode(Failure.self, forKey: .error)
|
||||
self = .failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Subscribers.Completion {
|
||||
|
||||
/// Erases the `Failure` type to `Swift.Error`. This function exists
|
||||
|
||||
@@ -20,28 +20,37 @@ extension Subscribers {
|
||||
Codable,
|
||||
CustomStringConvertible
|
||||
{
|
||||
private var rawValue: UInt
|
||||
@usableFromInline
|
||||
internal let rawValue: UInt
|
||||
|
||||
private static let _rawValueUnlimited = UInt(Int.max) + 1
|
||||
|
||||
private init<Integer: BinaryInteger>(_ rawValue: Integer) {
|
||||
if rawValue < 0 {
|
||||
self.rawValue = 0
|
||||
} else if rawValue > Demand._rawValueUnlimited {
|
||||
self.rawValue = Demand._rawValueUnlimited
|
||||
} else {
|
||||
self.rawValue = UInt(rawValue)
|
||||
}
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
internal init(rawValue: UInt) {
|
||||
self.rawValue = min(UInt(Int.max) + 1, rawValue)
|
||||
}
|
||||
|
||||
/// Requests as many values as the `Publisher` can produce.
|
||||
public static let unlimited = Subscribers.Demand(_rawValueUnlimited)
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static var unlimited: Demand {
|
||||
return Demand(rawValue: .max)
|
||||
}
|
||||
|
||||
/// A demand for no items.
|
||||
///
|
||||
/// This is equivalent to `Demand.max(0)`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static var none: Demand { return .max(0) }
|
||||
|
||||
/// Limits the maximum number of values.
|
||||
/// The `Publisher` may send fewer than the requested number.
|
||||
/// Negative values will result in a `fatalError`.
|
||||
public static func max(_ value: Int) -> Subscribers.Demand {
|
||||
return .init(UInt(value))
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func max(_ value: Int) -> Demand {
|
||||
precondition(value >= 0, "demand cannot be negative")
|
||||
return Demand(rawValue: UInt(value))
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
@@ -53,6 +62,8 @@ extension Subscribers {
|
||||
}
|
||||
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func + (lhs: Demand, rhs: Demand) -> Demand {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, _):
|
||||
@@ -60,31 +71,34 @@ extension Subscribers {
|
||||
case (_, .unlimited):
|
||||
return .unlimited
|
||||
default:
|
||||
let (sum, isOverflow) = lhs.rawValue.addingReportingOverflow(rhs.rawValue)
|
||||
return isOverflow ? .unlimited : .init(sum)
|
||||
let (sum, isOverflow) = Int(lhs.rawValue)
|
||||
.addingReportingOverflow(Int(rhs.rawValue))
|
||||
return isOverflow ? .unlimited : .max(sum)
|
||||
}
|
||||
}
|
||||
|
||||
/// A demand for no items.
|
||||
///
|
||||
/// This is equivalent to `Demand.max(0)`.
|
||||
public static let none: Demand = .max(0)
|
||||
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func += (lhs: inout Demand, rhs: Demand) {
|
||||
if lhs == .unlimited { return }
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
|
||||
/// When adding any value to` .unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func + (lhs: Demand, rhs: Int) -> Demand {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
return Demand(lhs.rawValue.advanced(by: rhs))
|
||||
let (sum, isOverflow) = Int(lhs.rawValue).addingReportingOverflow(rhs)
|
||||
return isOverflow ? .unlimited : .max(sum)
|
||||
}
|
||||
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func += (lhs: inout Demand, rhs: Int) {
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
@@ -93,21 +107,23 @@ extension Subscribers {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
let (product, isOverflow) =
|
||||
lhs.rawValue.multipliedReportingOverflow(by: UInt(rhs))
|
||||
|
||||
return isOverflow ? .unlimited : .init(product)
|
||||
let (product, isOverflow) = Int(lhs.rawValue)
|
||||
.multipliedReportingOverflow(by: rhs)
|
||||
return isOverflow ? .unlimited : .max(product)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func *= (lhs: inout Demand, rhs: Int) {
|
||||
lhs = lhs * rhs
|
||||
}
|
||||
|
||||
/// When subtracting any value (including .unlimited) from .unlimited,
|
||||
/// the result is still .unlimited. Subtracting unlimited from any value
|
||||
/// (except unlimited) results in .max(0). A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to .max(0).
|
||||
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
|
||||
/// the result is still `.unlimited`. Subtracting `.unlimited` from any value
|
||||
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to `.max(0)`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func - (lhs: Demand, rhs: Demand) -> Demand {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, _):
|
||||
@@ -115,110 +131,231 @@ extension Subscribers {
|
||||
case (_, .unlimited):
|
||||
return .none
|
||||
default:
|
||||
let (difference, isOverflow) =
|
||||
lhs.rawValue.subtractingReportingOverflow(rhs.rawValue)
|
||||
return isOverflow ? .none : .init(difference)
|
||||
let (difference, isOverflow) = Int(lhs.rawValue)
|
||||
.subtractingReportingOverflow(Int(rhs.rawValue))
|
||||
return isOverflow ? .none : .max(difference)
|
||||
}
|
||||
}
|
||||
|
||||
/// When subtracting any value (including .unlimited) from .unlimited,
|
||||
/// the result is still .unlimited. Subtracting unlimited from any value
|
||||
/// (except unlimited) results in .max(0). A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to .max(0).
|
||||
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
|
||||
/// the result is still `.unlimited`. Subtracting unlimited from any value
|
||||
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to `.max(0)`.
|
||||
/// but be aware that it is not usable when requesting values in a subscription.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func -= (lhs: inout Demand, rhs: Demand) {
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
/// When subtracting any value from .unlimited, the result is still .unlimited.
|
||||
/// When subtracting any value from `.unlimited`, the result is still
|
||||
/// `.unlimited`.
|
||||
/// A negative demand is not possible; any operation that would result in
|
||||
/// a negative value is clamped to .max(0)
|
||||
/// a negative value is clamped to `.max(0)`
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func - (lhs: Demand, rhs: Int) -> Demand {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
let (difference, isOverflow) =
|
||||
Int(lhs.rawValue).subtractingReportingOverflow(rhs)
|
||||
return isOverflow ? .none : .init(difference)
|
||||
let (difference, isOverflow) = Int(lhs.rawValue)
|
||||
.subtractingReportingOverflow(rhs)
|
||||
return isOverflow ? .none : .max(difference)
|
||||
}
|
||||
|
||||
/// When subtracting any value from .unlimited, the result is still .unlimited.
|
||||
/// When subtracting any value from `.unlimited,` the result is still
|
||||
/// `.unlimited`.
|
||||
/// A negative demand is not possible; any operation that would result in
|
||||
/// a negative value is clamped to .max(0)
|
||||
/// a negative value is clamped to `.max(0)`
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func -= (lhs: inout Demand, rhs: Int) {
|
||||
if lhs == .unlimited { return }
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs > .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(lhs.rawValue) > rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs >= .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(lhs.rawValue) >= rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) > rhs
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return lhs > Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) >= rhs
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return lhs >= Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs < .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return Int(lhs.rawValue) < rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) < rhs
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return lhs < Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs <= .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return Int(lhs.rawValue) <= rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) <= rhs
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return lhs <= Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// If `lhs` is `.unlimited`, then the result is always `false`.
|
||||
/// If `rhs` is `.unlimited` then the result is `false` iff `lhs` is `.unlimited`
|
||||
/// Otherwise, the two `.max` values are compared.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Demand, rhs: Demand) -> Bool {
|
||||
if lhs == .unlimited {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, _):
|
||||
return false
|
||||
}
|
||||
|
||||
if rhs == .unlimited {
|
||||
case (_, .unlimited):
|
||||
return true
|
||||
default:
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Demand, rhs: Demand) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, .unlimited):
|
||||
return true
|
||||
case (.unlimited, _):
|
||||
return false
|
||||
case (_, .unlimited):
|
||||
return true
|
||||
default:
|
||||
return lhs.rawValue <= rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Demand, rhs: Demand) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, .unlimited):
|
||||
return true
|
||||
case (.unlimited, _):
|
||||
return true
|
||||
case (_, .unlimited):
|
||||
return false
|
||||
default:
|
||||
return lhs.rawValue >= rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Demand, rhs: Demand) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, .unlimited):
|
||||
return false
|
||||
case (.unlimited, _):
|
||||
return true
|
||||
case (_, .unlimited):
|
||||
return false
|
||||
default:
|
||||
return lhs.rawValue > rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
|
||||
/// integer.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func == (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs == .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return Int(lhs.rawValue) == rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
|
||||
/// any integer.
|
||||
public static func != (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs != .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(lhs.rawValue) != rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
|
||||
/// integer.
|
||||
public static func == (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) == rhs
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return rhs.rawValue == lhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
|
||||
/// any integer.
|
||||
public static func != (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) != rhs
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(rhs.rawValue) != lhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of requested values, or `nil` if `.unlimited`.
|
||||
@@ -229,5 +366,14 @@ extension Subscribers {
|
||||
return Int(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
try self.init(rawValue: decoder.singleValueContainer().decode(UInt.self))
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,16 @@ extension Subscribers {
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
// NOTE: this class has been audited for thread safety.
|
||||
// Combine doesn't use any locking here.
|
||||
|
||||
/// The closure to execute on receipt of a value.
|
||||
public let receiveValue: (Input) -> Void
|
||||
|
||||
/// The closure to execute on completion.
|
||||
public let receiveCompletion: (Subscribers.Completion<Failure>) -> Void
|
||||
|
||||
private var _upstreamSubscription: Subscription?
|
||||
private var status = SubscriptionStatus.awaitingSubscription
|
||||
|
||||
public var description: String { return "Sink" }
|
||||
|
||||
@@ -34,22 +37,23 @@ extension Subscribers {
|
||||
/// Initializes a sink with the provided closures.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveValue: The closure to execute on receipt of a value. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
/// - receiveCompletion: The closure to execute on completion. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
public init(receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
|
||||
receiveValue: @escaping ((Input) -> Void)) {
|
||||
self.receiveCompletion = receiveCompletion ?? { _ in }
|
||||
/// - receiveCompletion: The closure to execute on completion.
|
||||
/// - receiveValue: The closure to execute on receipt of a value.
|
||||
public init(
|
||||
receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void,
|
||||
receiveValue: @escaping ((Input) -> Void)
|
||||
) {
|
||||
self.receiveCompletion = receiveCompletion
|
||||
self.receiveValue = receiveValue
|
||||
}
|
||||
|
||||
public func receive(subscription: Subscription) {
|
||||
if _upstreamSubscription == nil {
|
||||
_upstreamSubscription = subscription
|
||||
subscription.request(.unlimited)
|
||||
} else {
|
||||
switch status {
|
||||
case .subscribed, .terminal:
|
||||
subscription.cancel()
|
||||
case .awaitingSubscription:
|
||||
status = .subscribed(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,11 +64,15 @@ extension Subscribers {
|
||||
|
||||
public func receive(completion: Subscribers.Completion<Failure>) {
|
||||
receiveCompletion(completion)
|
||||
status = .terminal
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
_upstreamSubscription?.cancel()
|
||||
_upstreamSubscription = nil
|
||||
guard case let .subscribed(subscription) = status else {
|
||||
return
|
||||
}
|
||||
subscription.cancel()
|
||||
status = .terminal
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,21 +83,44 @@ extension Publisher {
|
||||
///
|
||||
/// This method creates the subscriber and immediately requests an unlimited number
|
||||
/// of values, prior to returning the subscriber.
|
||||
/// - parameter receiveValue: The closure to execute on receipt of a value. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
/// - parameter receiveComplete: The closure to execute on completion. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
/// - Returns: A subscriber that performs the provided closures upon receiving values
|
||||
/// or completion.
|
||||
///
|
||||
/// - parameter receiveComplete: The closure to execute on completion.
|
||||
/// - parameter receiveValue: The closure to execute on receipt of a value.
|
||||
/// - Returns: A cancellable instance; used when you end assignment of
|
||||
/// the received value. Deallocation of the result will tear down
|
||||
/// the subscription stream.
|
||||
public func sink(
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
|
||||
receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void,
|
||||
receiveValue: @escaping ((Output) -> Void)
|
||||
) -> Subscribers.Sink<Output, Failure> {
|
||||
) -> AnyCancellable {
|
||||
let subscriber = Subscribers.Sink<Output, Failure>(
|
||||
receiveCompletion: receiveCompletion,
|
||||
receiveValue: receiveValue
|
||||
)
|
||||
subscribe(subscriber)
|
||||
return subscriber
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Attaches a subscriber with closure-based behavior.
|
||||
///
|
||||
/// This method creates the subscriber and immediately requests an unlimited number
|
||||
/// of values, prior to returning the subscriber.
|
||||
///
|
||||
/// - parameter receiveValue: The closure to execute on receipt of a value.
|
||||
/// - Returns: A cancellable instance; used when you end assignment of
|
||||
/// the received value. Deallocation of the result will tear down
|
||||
/// the subscription stream.
|
||||
public func sink(
|
||||
receiveValue: @escaping (Output) -> Void
|
||||
) -> AnyCancellable {
|
||||
let subscriber = Subscribers.Sink<Output, Failure>(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: receiveValue
|
||||
)
|
||||
subscribe(subscriber)
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,20 +13,20 @@ extension Subscriptions {
|
||||
///
|
||||
/// Use the empty subscription when you need a `Subscription` that ignores requests
|
||||
/// and cancellation.
|
||||
public static var empty: Subscription { return Empty.shared }
|
||||
public static var empty: Subscription { return EmptySubscription.shared }
|
||||
}
|
||||
|
||||
private final class Empty: Subscription, CustomStringConvertible, CustomReflectable {
|
||||
|
||||
private final class EmptySubscription: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private init() {}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {}
|
||||
|
||||
func cancel() {}
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return CombineIdentifier() }
|
||||
|
||||
static let shared = Empty()
|
||||
fileprivate static let shared = EmptySubscription()
|
||||
|
||||
var description: String { return "Empty" }
|
||||
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
//
|
||||
// DispatchQueue.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 21.08.2019.
|
||||
//
|
||||
|
||||
import Dispatch
|
||||
import OpenCombine
|
||||
|
||||
extension DispatchQueue {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Combine extends `DispatchQueue` with new methods and nested types.
|
||||
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
|
||||
/// e. g. when importing Foundation), you will not be able
|
||||
/// to write `DispatchQueue.SchedulerTimeType`,
|
||||
/// because Swift is unable to understand which `SchedulerTimeType`
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `DispatchQueue.OCombine.SchedulerTimeType`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine: Scheduler {
|
||||
|
||||
public let queue: DispatchQueue
|
||||
|
||||
public init(_ queue: DispatchQueue) {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
/// The scheduler time type used by the dispatch queue.
|
||||
public struct SchedulerTimeType: Strideable, Codable, Hashable {
|
||||
|
||||
/// The dispatch time represented by this type.
|
||||
public var dispatchTime: DispatchTime
|
||||
|
||||
/// Creates a dispatch queue time type instance.
|
||||
///
|
||||
/// - Parameter time: The dispatch time to represent.
|
||||
public init(_ time: DispatchTime) {
|
||||
dispatchTime = time
|
||||
}
|
||||
|
||||
/// Returns the distance to another dispatch queue time.
|
||||
///
|
||||
/// - Parameter other: Another dispatch queue time.
|
||||
/// - Returns: The time interval between this time and the provided time.
|
||||
public func distance(to other: SchedulerTimeType) -> Stride {
|
||||
return .nanoseconds(
|
||||
Int(other.dispatchTime.rawValue - dispatchTime.rawValue)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a dispatch queue scheduler time calculated by advancing
|
||||
/// this instance’s time by the given interval.
|
||||
///
|
||||
/// - Parameter n: A time interval to advance.
|
||||
/// - Returns: A dispatch queue time advanced by the given
|
||||
/// interval from this instance’s time.
|
||||
public func advanced(by stride: Stride) -> SchedulerTimeType {
|
||||
return .init(dispatchTime + stride.timeInterval)
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(dispatchTime.rawValue)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(dispatchTime.uptimeNanoseconds)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
dispatchTime = try .init(uptimeNanoseconds: container.decode(UInt64.self))
|
||||
}
|
||||
|
||||
/// A type that represents the distance between two values.
|
||||
public struct Stride: SchedulerTimeIntervalConvertible,
|
||||
Comparable,
|
||||
SignedNumeric,
|
||||
ExpressibleByFloatLiteral,
|
||||
Hashable,
|
||||
Codable {
|
||||
|
||||
/// If created via floating point literal, the value is
|
||||
/// converted to nanoseconds via multiplication.
|
||||
public typealias FloatLiteralType = Double
|
||||
|
||||
/// Nanoseconds, same as DispatchTimeInterval.
|
||||
public typealias IntegerLiteralType = Int
|
||||
|
||||
/// A type that can represent the absolute value of any possible
|
||||
/// value of the conforming type.
|
||||
public typealias Magnitude = Int
|
||||
|
||||
/// The value of this time interval in nanoseconds.
|
||||
public var magnitude: Int
|
||||
|
||||
/// A `DispatchTimeInterval` created with the value of this type
|
||||
/// in nanoseconds.
|
||||
public var timeInterval: DispatchTimeInterval {
|
||||
return .nanoseconds(magnitude)
|
||||
}
|
||||
|
||||
private init(magnitude: Int) {
|
||||
self.magnitude = magnitude
|
||||
}
|
||||
|
||||
/// Creates a dispatch queue time interval from the given
|
||||
/// dispatch time interval.
|
||||
///
|
||||
/// - Parameter timeInterval: A dispatch time interval.
|
||||
public init(_ timeInterval: DispatchTimeInterval) {
|
||||
switch timeInterval {
|
||||
case .seconds(let seconds):
|
||||
self = .seconds(seconds)
|
||||
case .milliseconds(let milliseconds):
|
||||
self = .milliseconds(milliseconds)
|
||||
case .microseconds(let microseconds):
|
||||
self = .microseconds(microseconds)
|
||||
case .nanoseconds(let nanoseconds):
|
||||
self = .nanoseconds(nanoseconds)
|
||||
case .never:
|
||||
fallthrough
|
||||
@unknown default:
|
||||
self = .nanoseconds(.max)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a dispatch queue time interval from a floating-point
|
||||
/// seconds value.
|
||||
///
|
||||
/// - Parameter value: The number of seconds, as a `Double`.
|
||||
public init(floatLiteral value: Double) {
|
||||
self = .seconds(value)
|
||||
}
|
||||
|
||||
/// Creates a dispatch queue time interval from an integer seconds value.
|
||||
///
|
||||
/// - Parameter value: The number of seconds, as an `Int`.
|
||||
public init(integerLiteral value: Int) {
|
||||
self = .seconds(value)
|
||||
}
|
||||
|
||||
/// Creates a dispatch queue time interval from a binary integer type.
|
||||
///
|
||||
/// If `exactly` cannot convert to an `Int`, the resulting time interval
|
||||
/// is `nil`.
|
||||
///
|
||||
/// - 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)
|
||||
}
|
||||
|
||||
public static func < (lhs: Stride, rhs: Stride) -> Bool {
|
||||
return lhs.magnitude < rhs.magnitude
|
||||
}
|
||||
|
||||
public static func * (lhs: Stride, rhs: Stride) -> Stride {
|
||||
// A bug in Combine, should be nanoseconds (FB7189676)
|
||||
return .seconds(lhs.magnitude * rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func + (lhs: Stride, rhs: Stride) -> Stride {
|
||||
// A bug in Combine, should be nanoseconds (FB7189676)
|
||||
return .seconds(lhs.magnitude + rhs.magnitude)
|
||||
}
|
||||
|
||||
public static func - (lhs: Stride, rhs: Stride) -> Stride {
|
||||
// A bug in Combine, should be nanoseconds (FB7189676)
|
||||
return .seconds(lhs.magnitude - rhs.magnitude)
|
||||
}
|
||||
|
||||
// swiftlint:disable shorthand_operator
|
||||
|
||||
public static func -= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
public static func *= (lhs: inout Stride, rhs: Stride) {
|
||||
lhs = lhs * rhs
|
||||
}
|
||||
|
||||
public static func += (lhs: inout Stride, rhs: Stride) {
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
|
||||
// swiftlint:enable shorthand_operator
|
||||
|
||||
public static func seconds(_ value: Double) -> Stride {
|
||||
return Stride(magnitude: Int(value * 1_000_000_000))
|
||||
}
|
||||
|
||||
public static func seconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value * 1_000_000_000)
|
||||
}
|
||||
|
||||
public static func milliseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value * 1_000_000)
|
||||
}
|
||||
|
||||
public static func microseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value * 1_000)
|
||||
}
|
||||
|
||||
public static func nanoseconds(_ value: Int) -> Stride {
|
||||
return Stride(magnitude: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Options that affect the operation of the dispatch queue scheduler.
|
||||
public struct SchedulerOptions {
|
||||
|
||||
/// The dispatch queue quality of service.
|
||||
public var qos: DispatchQoS
|
||||
|
||||
/// The dispatch queue work item flags.
|
||||
public var flags: DispatchWorkItemFlags
|
||||
|
||||
/// The dispatch group, if any, that should be used for performing actions.
|
||||
public var group: DispatchGroup?
|
||||
|
||||
public init(qos: DispatchQoS = .unspecified,
|
||||
flags: DispatchWorkItemFlags = [],
|
||||
group: DispatchGroup? = nil) {
|
||||
self.qos = qos
|
||||
self.flags = flags
|
||||
self.group = group
|
||||
}
|
||||
}
|
||||
|
||||
public var minimumTolerance: SchedulerTimeType.Stride {
|
||||
return .nanoseconds(0)
|
||||
}
|
||||
|
||||
public var now: SchedulerTimeType {
|
||||
return .init(.now())
|
||||
}
|
||||
|
||||
public func schedule(options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
let options = options ?? .init()
|
||||
queue.async(group: options.group,
|
||||
qos: options.qos,
|
||||
flags: options.flags,
|
||||
execute: action)
|
||||
}
|
||||
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
let options = options ?? .init()
|
||||
queue.asyncAfter(deadline: date.dispatchTime,
|
||||
qos: options.qos,
|
||||
flags: options.flags,
|
||||
execute: action)
|
||||
}
|
||||
|
||||
/// Performs the action at some time after the specified date, at the specified
|
||||
/// frequency, optionally taking into account tolerance if possible.
|
||||
public func schedule(after date: SchedulerTimeType,
|
||||
interval: SchedulerTimeType.Stride,
|
||||
tolerance: SchedulerTimeType.Stride,
|
||||
options: SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
let options = options ?? .init()
|
||||
let timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
timer.setEventHandler(qos: options.qos,
|
||||
flags: options.flags,
|
||||
handler: action)
|
||||
timer.schedule(deadline: date.dispatchTime,
|
||||
repeating: interval.timeInterval,
|
||||
leeway: tolerance.timeInterval)
|
||||
timer.resume()
|
||||
return AnyCancellable(timer.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Combine extends `DispatchQueue` with new methods and nested types.
|
||||
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
|
||||
/// e. g. when importing Foundation), you will not be able
|
||||
/// to write `DispatchQueue.main.schedule { doThings() }`,
|
||||
/// because Swift is unable to understand which `schedule` method
|
||||
/// you're referring to.
|
||||
///
|
||||
/// So you have to write `DispatchQueue.main.ocombine.schedule { doThings() }`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public var ocombine: OCombine {
|
||||
return OCombine(self)
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
extension DispatchQueue: OpenCombine.Scheduler {
|
||||
|
||||
public typealias SchedulerOptions = OCombine.SchedulerOptions
|
||||
|
||||
public typealias SchedulerTimeType = OCombine.SchedulerTimeType
|
||||
|
||||
public var minimumTolerance: OCombine.SchedulerTimeType.Stride {
|
||||
return ocombine.minimumTolerance
|
||||
}
|
||||
|
||||
public var now: OCombine.SchedulerTimeType {
|
||||
return ocombine.now
|
||||
}
|
||||
|
||||
public func schedule(options: OCombine.SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
ocombine.schedule(options: options, action)
|
||||
}
|
||||
|
||||
public func schedule(after date: OCombine.SchedulerTimeType,
|
||||
tolerance: OCombine.SchedulerTimeType.Stride,
|
||||
options: OCombine.SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) {
|
||||
ocombine.schedule(after: date, tolerance: tolerance, options: options, action)
|
||||
}
|
||||
|
||||
public func schedule(after date: OCombine.SchedulerTimeType,
|
||||
interval: OCombine.SchedulerTimeType.Stride,
|
||||
tolerance: OCombine.SchedulerTimeType.Stride,
|
||||
options: OCombine.SchedulerOptions?,
|
||||
_ action: @escaping () -> Void) -> Cancellable {
|
||||
return ocombine.schedule(after: date,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
options: options,
|
||||
action)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Subscribers.Demand.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
import OpenCombineTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += OpenCombineTests.allTests()
|
||||
XCTMain(tests)
|
||||
@@ -13,16 +13,9 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AnyCancellableTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testClosureInitialized", testClosureInitialized),
|
||||
("testCancelableInitialized", testCancelableInitialized),
|
||||
("testCancelTwice", testCancelTwice),
|
||||
("testStoreInArbitraryCollection", testStoreInArbitraryCollection),
|
||||
]
|
||||
|
||||
func testClosureInitialized() {
|
||||
|
||||
var fired = false
|
||||
@@ -97,11 +90,56 @@ final class AnyCancellableTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(disposeBag.history, [.emptyInit, .append])
|
||||
|
||||
let cancellable2 = AnyCancellable({})
|
||||
let cancellable2 = AnyCancellable(cancellable1)
|
||||
cancellable2.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag.history, [.emptyInit, .append, .append])
|
||||
|
||||
XCTAssertEqual(disposeBag.storage, [cancellable1, cancellable2])
|
||||
|
||||
let cancellable2Abstracted: Cancellable = cancellable2
|
||||
cancellable2Abstracted.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag.history, [.emptyInit, .append, .append, .append])
|
||||
XCTAssertEqual(disposeBag.storage.count, 3)
|
||||
|
||||
if disposeBag.storage.count == 3 {
|
||||
XCTAssertNotEqual(disposeBag.storage[2], cancellable2)
|
||||
}
|
||||
}
|
||||
|
||||
func testStoreInSet() {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let cancellable1 = AnyCancellable({})
|
||||
cancellable1.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag, [cancellable1])
|
||||
|
||||
let cancellable2 = AnyCancellable(cancellable1)
|
||||
cancellable2.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag, [cancellable1, cancellable2])
|
||||
|
||||
cancellable2.store(in: &disposeBag)
|
||||
XCTAssertEqual(disposeBag, [cancellable1, cancellable2])
|
||||
|
||||
let cancellable2Abstracted: Cancellable = cancellable2
|
||||
cancellable2Abstracted.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag.count, 3)
|
||||
}
|
||||
|
||||
func testIndirectCancellation() {
|
||||
let subscription = CustomSubscription()
|
||||
let cancellable1 = AnyCancellable(subscription)
|
||||
let cancellable2 = AnyCancellable(cancellable1)
|
||||
XCTAssert(subscription.history.isEmpty)
|
||||
|
||||
cancellable2.cancel()
|
||||
XCTAssertEqual(subscription.history, [.cancelled])
|
||||
|
||||
cancellable1.cancel()
|
||||
XCTAssertEqual(subscription.history, [.cancelled])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,22 +13,28 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AnyPublisherTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testErasePublisher", testErasePublisher),
|
||||
]
|
||||
|
||||
private typealias Sut = AnyPublisher<Int, TestingError>
|
||||
|
||||
func testErasePublisher() {
|
||||
|
||||
let publisher = TrackingSubject<Int>()
|
||||
let erased = AnyPublisher(publisher)
|
||||
let subscriber = TrackingSubscriber()
|
||||
let publisher = TrackingSubject<Int>(
|
||||
receiveSubscriber: {
|
||||
XCTAssertEqual($0.combineIdentifier, subscriber.combineIdentifier)
|
||||
}
|
||||
)
|
||||
let erased = publisher.eraseToAnyPublisher()
|
||||
|
||||
erased.receive(subscriber: subscriber)
|
||||
XCTAssertEqual(publisher.history, [.subscriber(subscriber.combineIdentifier)])
|
||||
erased.subscribe(subscriber)
|
||||
XCTAssertEqual(publisher.history, [.subscriber])
|
||||
}
|
||||
|
||||
func testDescription() {
|
||||
let erased = AnyPublisher(TrackingSubject<Int>())
|
||||
XCTAssertEqual(erased.description, "AnyPublisher")
|
||||
XCTAssertEqual(erased.description, erased.playgroundDescription as? String)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
//
|
||||
// AnySubjectTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class AnySubjectTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testEraseSubject", testEraseSubject),
|
||||
("testClosureBasedSubject", testClosureBasedSubject),
|
||||
]
|
||||
|
||||
private typealias Sut = AnyPublisher<Int, TestingError>
|
||||
|
||||
func testEraseSubject() {
|
||||
|
||||
let subject = TrackingSubject<Int>()
|
||||
let erased = AnySubject(subject)
|
||||
let subscriber = TrackingSubscriber()
|
||||
|
||||
erased.receive(subscriber: subscriber)
|
||||
erased.send(42)
|
||||
erased.send(completion: .finished)
|
||||
erased.send(completion: .failure("f"))
|
||||
erased.send(12)
|
||||
erased.receive(subscriber: subscriber)
|
||||
|
||||
XCTAssertEqual(subject.history, [.subscriber(subscriber.combineIdentifier),
|
||||
.value(42),
|
||||
.completion(.finished),
|
||||
.completion(.failure("f")),
|
||||
.value(12),
|
||||
.subscriber(subscriber.combineIdentifier)])
|
||||
}
|
||||
|
||||
func testClosureBasedSubject() {
|
||||
|
||||
var events: [TrackingSubject<Int>.Event] = []
|
||||
|
||||
let erased = AnySubject<Int, TestingError>(
|
||||
{ events.append(.subscriber($0.combineIdentifier)) },
|
||||
{ events.append(.value($0)) },
|
||||
{ events.append(.completion($0)) }
|
||||
)
|
||||
let subscriber = TrackingSubscriber()
|
||||
|
||||
erased.receive(subscriber: subscriber)
|
||||
erased.send(42)
|
||||
erased.send(completion: .finished)
|
||||
erased.send(completion: .failure("f"))
|
||||
erased.send(12)
|
||||
erased.receive(subscriber: subscriber)
|
||||
|
||||
XCTAssertEqual(events, [.subscriber(subscriber.combineIdentifier),
|
||||
.value(42),
|
||||
.completion(.finished),
|
||||
.completion(.failure("f")),
|
||||
.value(12),
|
||||
.subscriber(subscriber.combineIdentifier)])
|
||||
}
|
||||
}
|
||||
@@ -13,22 +13,12 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private typealias Sut = AnySubscriber<Int, TestingError>
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testCombineIdentifier", testCombineIdentifier),
|
||||
("testDescription", testDescription),
|
||||
("testReflection", testReflection),
|
||||
("testErasingSubscriber", testErasingSubscriber),
|
||||
("testErasingSubscriberSubscription", testErasingSubscriberSubscription),
|
||||
("testErasingSubject", testErasingSubject),
|
||||
("testErasingSubjectSubscription", testErasingSubjectSubscription),
|
||||
]
|
||||
|
||||
func testCombineIdentifier() {
|
||||
|
||||
let empty = Sut()
|
||||
@@ -38,18 +28,28 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
let subscriber1 = TrackingSubscriber()
|
||||
let subscriber2 = TrackingSubscriber()
|
||||
XCTAssertNotEqual(Sut(subscriber1).combineIdentifier,
|
||||
Sut(subscriber2).combineIdentifier)
|
||||
XCTAssertEqual(subscriber1.combineIdentifier,
|
||||
Sut(subscriber1).combineIdentifier)
|
||||
XCTAssertEqual(subscriber2.combineIdentifier,
|
||||
Sut(subscriber2).combineIdentifier)
|
||||
|
||||
do {
|
||||
let erased1 = Sut(subscriber1)
|
||||
let erased2 = Sut(subscriber2)
|
||||
XCTAssertNotEqual(erased1.combineIdentifier, erased2.combineIdentifier)
|
||||
}
|
||||
|
||||
do {
|
||||
let subject = Sut(subscriber1)
|
||||
XCTAssertEqual(subscriber1.combineIdentifier, subject.combineIdentifier)
|
||||
}
|
||||
|
||||
do {
|
||||
let subject = Sut(subscriber2)
|
||||
XCTAssertEqual(subscriber2.combineIdentifier, subject.combineIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
func testDescription() {
|
||||
|
||||
let empty = Sut()
|
||||
XCTAssertEqual(empty.description, "AnySubscriber")
|
||||
XCTAssertEqual(empty.description, "Anonymous AnySubscriber")
|
||||
XCTAssertEqual(empty.description, empty.playgroundDescription as? String)
|
||||
|
||||
let subject = PassthroughSubject<Int, TestingError>()
|
||||
@@ -71,7 +71,7 @@ final class AnySubscriberTests: XCTestCase {
|
||||
let empty = Sut()
|
||||
XCTAssertEqual(
|
||||
String(describing: Mirror(reflecting: empty).subjectType),
|
||||
"CombineIdentifier"
|
||||
"String"
|
||||
)
|
||||
XCTAssert(Mirror(reflecting: empty).children.isEmpty)
|
||||
|
||||
@@ -123,7 +123,7 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
publishEvents(events, publisher)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
@@ -136,20 +136,12 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
publishEvents(events, erased)
|
||||
|
||||
let expectedEvents: [TrackingSubject.Event] =
|
||||
events.compactMap(subscriberEventToSubjectEvent)
|
||||
let expectedEvents: [TrackingSubject<Int>.Event] =
|
||||
[.subscription("Subject")] + events.compactMap(subscriberEventToSubjectEvent)
|
||||
.throughFirstCompletion()
|
||||
|
||||
XCTAssertEqual(subject.history, expectedEvents)
|
||||
|
||||
let shuffledEvents = events.shuffled()
|
||||
|
||||
publishEvents(shuffledEvents, erased)
|
||||
|
||||
let expectedShuffledEvents =
|
||||
shuffledEvents.compactMap(subscriberEventToSubjectEvent)
|
||||
|
||||
XCTAssertEqual(subject.history, expectedEvents + expectedShuffledEvents)
|
||||
|
||||
let demand = erased.receive(0)
|
||||
|
||||
XCTAssertEqual(demand, .none)
|
||||
@@ -167,19 +159,16 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
publishEvents(events, publisher)
|
||||
|
||||
XCTAssertEqual(subject.history, [.value(31),
|
||||
.value(42),
|
||||
.value(-1),
|
||||
.value(141241241),
|
||||
XCTAssertEqual(subject.history, [.subscription("Subject"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private let events: [TrackingSubscriber.Event] = [
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription("1"),
|
||||
.subscription("2"),
|
||||
.subscription("3"),
|
||||
.value(31),
|
||||
.value(42),
|
||||
.value(-1),
|
||||
@@ -189,7 +178,7 @@ private let events: [TrackingSubscriber.Event] = [
|
||||
.completion(.failure("failure"))
|
||||
]
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func publishEvents(_ events: [TrackingSubscriber.Event], _ erased: Sut) {
|
||||
for event in events {
|
||||
switch event {
|
||||
@@ -203,15 +192,15 @@ private func publishEvents(_ events: [TrackingSubscriber.Event], _ erased: Sut)
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func publishEvents(
|
||||
_ events: [TrackingSubscriber.Event],
|
||||
_ publisher: PassthroughSubject<Int, TestingError>
|
||||
) {
|
||||
for event in events {
|
||||
switch event {
|
||||
case .subscription:
|
||||
break
|
||||
case .subscription(let s):
|
||||
publisher.send(subscription: s)
|
||||
case .value(let v):
|
||||
publisher.send(v)
|
||||
case .completion(let c):
|
||||
@@ -220,7 +209,7 @@ private func publishEvents(
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func subscriberEventToSubjectEvent(
|
||||
_ from: TrackingSubscriber.Event
|
||||
) -> TrackingSubject<Int>.Event? {
|
||||
@@ -233,3 +222,21 @@ private func subscriberEventToSubjectEvent(
|
||||
return .completion(c)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension Array {
|
||||
func throughFirstCompletion<SubjectOutput>() -> Array
|
||||
where Element == TrackingSubject<SubjectOutput>.Event
|
||||
{
|
||||
var encounteredFirstCompletion = false
|
||||
return self.prefix {
|
||||
if encounteredFirstCompletion {
|
||||
return false
|
||||
}
|
||||
if case .completion = $0 {
|
||||
encounteredFirstCompletion = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,9 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CombineIdentifierTests: PerformanceTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testDefaultInitialized", testDefaultInitialized),
|
||||
("testAnyObject", testAnyObject),
|
||||
("testDefaultInitializedPerformance", testDefaultInitializedPerformance),
|
||||
]
|
||||
|
||||
func testDefaultInitialized() {
|
||||
let id1 = CombineIdentifier()
|
||||
let id2 = CombineIdentifier()
|
||||
@@ -50,9 +44,9 @@ final class CombineIdentifierTests: PerformanceTestCase {
|
||||
}
|
||||
|
||||
func testDefaultInitializedPerformance() throws {
|
||||
try benchmark(allowFailure: isDebug, executionCount: 100) {
|
||||
try benchmark(allowFailure: isDebug, executionCount: 500) {
|
||||
for _ in 0..<2000 {
|
||||
_ = CombineIdentifier()
|
||||
blackHole(CombineIdentifier())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,9 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
// swiftlint:disable explicit_top_level_acl
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testRequestingDemand", testRequestingDemand),
|
||||
("testSendFailureCompletion", testSendFailureCompletion),
|
||||
("testMultipleSubscriptions", testMultipleSubscriptions),
|
||||
("testMultipleCompletions", testMultipleCompletions),
|
||||
("testValuesAfterCompletion", testValuesAfterCompletion),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testSynchronization", testSynchronization),
|
||||
]
|
||||
|
||||
private typealias Sut = CurrentValueSubject<Int, TestingError>
|
||||
|
||||
// Reactive Streams Spec: Rules #1, #2, #9
|
||||
@@ -114,6 +102,16 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
|
||||
}
|
||||
|
||||
func testCrashOnZeroInitialDemand() {
|
||||
assertCrashes {
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.none) }
|
||||
)
|
||||
|
||||
Sut(1).subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
func testSendFailureCompletion() {
|
||||
let cvs = Sut(0)
|
||||
let subscriber = TrackingSubscriber(
|
||||
@@ -124,18 +122,18 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0)])
|
||||
|
||||
cvs.value += 3
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0),
|
||||
.value(3)])
|
||||
|
||||
cvs.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0),
|
||||
.value(3),
|
||||
.completion(.failure(.oops))])
|
||||
@@ -179,51 +177,53 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.tracking.history, [.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.tracking.history,
|
||||
[.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject")])
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.tracking.history, [.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.tracking.history,
|
||||
[.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject")])
|
||||
}
|
||||
|
||||
// Reactive Streams Spec: Rule #6
|
||||
@@ -241,24 +241,24 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
cvs.value = 42
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42)])
|
||||
|
||||
cvs.send(completion: .finished)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
|
||||
cvs.send(completion: .finished)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
|
||||
cvs.send(completion: .failure("oops"))
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
@@ -279,23 +279,102 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
cvs.value = 44
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(44)])
|
||||
|
||||
cvs.send(completion: .finished)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(44),
|
||||
.completion(.finished)])
|
||||
|
||||
cvs.value = 1201
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(44),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSubscriptionAfterCompletion() {
|
||||
let passthrough = Sut(0)
|
||||
passthrough.send(completion: .finished)
|
||||
|
||||
let subscriber = TrackingSubscriber()
|
||||
passthrough.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSubscriptionAfterSend() {
|
||||
// Given
|
||||
let passthrough = Sut(0)
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: { subscription in
|
||||
subscription.request(.unlimited)
|
||||
})
|
||||
|
||||
// When
|
||||
passthrough.send(2)
|
||||
passthrough.subscribe(subscriber)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(2)])
|
||||
}
|
||||
|
||||
func testSubscriptionAfterSet() {
|
||||
// Given
|
||||
let passthrough = Sut(0)
|
||||
let subscriber = TrackingSubscriber(receiveSubscription: { subscription in
|
||||
subscription.request(.unlimited)
|
||||
})
|
||||
|
||||
// When
|
||||
passthrough.value = 3
|
||||
passthrough.subscribe(subscriber)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(3)])
|
||||
}
|
||||
|
||||
func testSendSubscription() {
|
||||
let subscription1 = CustomSubscription()
|
||||
let cvs = Sut(1)
|
||||
|
||||
cvs.send(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
|
||||
let subscriber1 = TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
|
||||
cvs.subscribe(subscriber1)
|
||||
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(subscriber1.history, [.subscription("CurrentValueSubject"),
|
||||
.value(1)])
|
||||
|
||||
let subscriber2 = TrackingSubscriber(receiveSubscription: { $0.request(.max(2)) })
|
||||
cvs.subscribe(subscriber2)
|
||||
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(subscriber1.history, [.subscription("CurrentValueSubject"),
|
||||
.value(1)])
|
||||
XCTAssertEqual(subscriber2.history, [.subscription("CurrentValueSubject"),
|
||||
.value(1)])
|
||||
|
||||
cvs.send(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
|
||||
cvs.send(0)
|
||||
cvs.send(0)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
cvs.send(subscription: subscription2)
|
||||
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
@@ -310,17 +389,17 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertTrue(subscriber.history.isEmpty)
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject")])
|
||||
|
||||
cvs.value += 1
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject")])
|
||||
|
||||
cvs.send(completion: .failure(.oops))
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
var subscription: Subscription?
|
||||
|
||||
@@ -332,18 +411,18 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
)
|
||||
XCTAssertTrue(subscriber.history.isEmpty)
|
||||
cvs.subscribe(subscriber)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0)])
|
||||
cvs.send(31)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0),
|
||||
.value(31)])
|
||||
XCTAssertNotNil(subscription)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
try XCTUnwrap(subscription).cancel()
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
|
||||
func testSynchronization() {
|
||||
@@ -380,15 +459,15 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
race(
|
||||
{
|
||||
cvs.value += 1
|
||||
cvs.value = 42
|
||||
},
|
||||
{
|
||||
cvs.value -= 1
|
||||
cvs.value = 42
|
||||
}
|
||||
)
|
||||
|
||||
XCTAssertEqual(inputs.value.count, 40200)
|
||||
XCTAssertEqual(cvs.value, 112)
|
||||
XCTAssertEqual(cvs.value, 42)
|
||||
|
||||
race(
|
||||
{
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
//
|
||||
// DispatchQueueSchedulerTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 26.08.2019.
|
||||
//
|
||||
|
||||
import Dispatch
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
import OpenCombineDispatch
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class DispatchQueueSchedulerTests: XCTestCase {
|
||||
|
||||
// MARK: - Scheduler.SchedulerTimeType
|
||||
|
||||
func testSchedulerTimeTypeDistance() {
|
||||
let time1 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let time2 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10431))
|
||||
|
||||
XCTAssertEqual(time1.distance(to: time2), .nanoseconds(431))
|
||||
|
||||
// A bug in Combine (FB7127210), caused by overflow on subtraction.
|
||||
// It should not crash. When they fix it, this test will fail and we'll know
|
||||
// that we need to update our implementation.
|
||||
assertCrashes {
|
||||
_ = time2.distance(to: time1)
|
||||
}
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeAdvanced() {
|
||||
let time = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let stride1 = Scheduler.SchedulerTimeType.Stride.nanoseconds(431)
|
||||
let stride2 = Scheduler.SchedulerTimeType.Stride.nanoseconds(-220)
|
||||
|
||||
XCTAssertEqual(time.advanced(by: stride1),
|
||||
Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10431)))
|
||||
|
||||
XCTAssertEqual(time.advanced(by: stride2),
|
||||
Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 9780)))
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeEquatable() {
|
||||
let time1 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let time2 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let time3 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10001))
|
||||
|
||||
XCTAssertEqual(time1, time1)
|
||||
XCTAssertEqual(time2, time2)
|
||||
XCTAssertEqual(time3, time3)
|
||||
|
||||
XCTAssertEqual(time1, time2)
|
||||
XCTAssertEqual(time2, time1)
|
||||
XCTAssertNotEqual(time1, time3)
|
||||
|
||||
assertCrashes {
|
||||
XCTAssertNotEqual(time3, time1)
|
||||
}
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeHashable() {
|
||||
let time1 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10000))
|
||||
let time2 = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 10001))
|
||||
|
||||
XCTAssertEqual(time1.hashValue, time1.dispatchTime.rawValue.hashValue)
|
||||
XCTAssertEqual(time2.hashValue, time2.dispatchTime.rawValue.hashValue)
|
||||
}
|
||||
|
||||
func testSchedulerTimeTypeCodable() throws {
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let time = Scheduler.SchedulerTimeType(.init(uptimeNanoseconds: 42))
|
||||
let encodedData = try encoder
|
||||
.encode(KeyedWrapper(value: time))
|
||||
let encodedString = String(decoding: encodedData, as: UTF8.self)
|
||||
|
||||
XCTAssertEqual(encodedString, #"{"value":42}"#)
|
||||
|
||||
let decodedTime = try decoder
|
||||
.decode(KeyedWrapper<Scheduler.SchedulerTimeType>.self, from: encodedData)
|
||||
.value
|
||||
|
||||
XCTAssertEqual(decodedTime, time)
|
||||
}
|
||||
|
||||
// MARK: - Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
func testStrideToDispatchTimeInterval() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
switch (Stride.seconds(12).timeInterval,
|
||||
Stride.milliseconds(34).timeInterval,
|
||||
Stride.microseconds(56).timeInterval,
|
||||
Stride.nanoseconds(78).timeInterval) {
|
||||
case (.nanoseconds(12000000000),
|
||||
.nanoseconds(34000000),
|
||||
.nanoseconds(56000),
|
||||
.nanoseconds(78)):
|
||||
break // pass
|
||||
case let intervals:
|
||||
XCTFail("Unexpected DispatchTimeInterval: \(intervals)")
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideFromDispatchTimeInterval() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual(Stride(.seconds(12)).magnitude, 12000000000)
|
||||
XCTAssertEqual(Stride(.milliseconds(34)).magnitude, 34000000)
|
||||
XCTAssertEqual(Stride(.microseconds(56)).magnitude, 56000)
|
||||
XCTAssertEqual(Stride(.nanoseconds(78)).magnitude, 78)
|
||||
XCTAssertEqual(Stride(.never).magnitude, .max)
|
||||
}
|
||||
|
||||
func testStrideFromNumericValue() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual(Stride.seconds(12.756).magnitude, 12756000000)
|
||||
XCTAssertEqual(Stride.seconds(34).magnitude, 34000000000)
|
||||
XCTAssertEqual(Stride.milliseconds(56).magnitude, 56000000)
|
||||
XCTAssertEqual(Stride.microseconds(78).magnitude, 78000)
|
||||
XCTAssertEqual(Stride.nanoseconds(90).magnitude, 90)
|
||||
|
||||
XCTAssertEqual((12.756 as Stride).magnitude, 12756000000)
|
||||
XCTAssertEqual((34 as Stride).magnitude, 34000000000)
|
||||
|
||||
XCTAssertNil(Stride(exactly: UInt64.max))
|
||||
XCTAssertEqual(Stride(exactly: 871 as UInt64)?.magnitude, 871)
|
||||
}
|
||||
|
||||
func testStrideComparable() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertLessThan(Stride.nanoseconds(1), .nanoseconds(2))
|
||||
XCTAssertGreaterThan(Stride.nanoseconds(-2), .microseconds(-10))
|
||||
XCTAssertLessThan(Stride.milliseconds(29), .seconds(29))
|
||||
}
|
||||
|
||||
func testStrideMultiplication() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) * .nanoseconds(61346)).magnitude, 0)
|
||||
XCTAssertEqual((Stride.nanoseconds(61346) * .nanoseconds(0)).magnitude, 0)
|
||||
XCTAssertEqual((Stride.nanoseconds(18) * .nanoseconds(1)).magnitude,
|
||||
18000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(18) * .microseconds(1)).magnitude,
|
||||
18000000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(1) * .nanoseconds(18)).magnitude,
|
||||
18000000000)
|
||||
XCTAssertEqual((Stride.microseconds(1) * .nanoseconds(18)).magnitude,
|
||||
18000000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(15) * .nanoseconds(2)).magnitude,
|
||||
30000000000)
|
||||
XCTAssertEqual((Stride.microseconds(-3) * .nanoseconds(10)).magnitude,
|
||||
-30000000000000)
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(0)
|
||||
stride *= .nanoseconds(61346)
|
||||
XCTAssertEqual(stride.magnitude, 0)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(61346)
|
||||
stride *= .nanoseconds(0)
|
||||
XCTAssertEqual(stride.magnitude, 0)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(18)
|
||||
stride *= .nanoseconds(1)
|
||||
XCTAssertEqual(stride.magnitude, 18000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(18)
|
||||
stride *= .microseconds(1)
|
||||
XCTAssertEqual(stride.magnitude, 18000000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(1)
|
||||
stride *= .nanoseconds(18)
|
||||
XCTAssertEqual(stride.magnitude, 18000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.microseconds(1)
|
||||
stride *= .nanoseconds(18)
|
||||
XCTAssertEqual(stride.magnitude, 18000000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(15)
|
||||
stride *= .nanoseconds(2)
|
||||
XCTAssertEqual(stride.magnitude, 30000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.microseconds(-3)
|
||||
stride *= .nanoseconds(10)
|
||||
XCTAssertEqual(stride.magnitude, -30000000000000)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideAddition() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) + .microseconds(2)).magnitude,
|
||||
2000000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(2) + .microseconds(0)).magnitude,
|
||||
2000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) + .nanoseconds(12)).magnitude,
|
||||
19000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(12) + .nanoseconds(7)).magnitude,
|
||||
19000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) + .nanoseconds(-12)).magnitude,
|
||||
-5000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(-12) + .nanoseconds(7)).magnitude,
|
||||
-5000000000)
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(0)
|
||||
stride += .microseconds(2)
|
||||
XCTAssertEqual(stride.magnitude, 2000000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(2)
|
||||
stride += .microseconds(0)
|
||||
XCTAssertEqual(stride.magnitude, 2000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride += .nanoseconds(12)
|
||||
XCTAssertEqual(stride.magnitude, 19000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(12)
|
||||
stride += .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, 19000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride += .nanoseconds(-12)
|
||||
XCTAssertEqual(stride.magnitude, -5000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(-12)
|
||||
stride += .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -5000000000)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideSubtraction() {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
XCTAssertEqual((Stride.nanoseconds(0) - .microseconds(2)).magnitude,
|
||||
-2000000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(2) - .microseconds(0)).magnitude,
|
||||
2000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) - .nanoseconds(12)).magnitude,
|
||||
-5000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(12) - .nanoseconds(7)).magnitude,
|
||||
5000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(7) - .nanoseconds(-12)).magnitude,
|
||||
19000000000)
|
||||
XCTAssertEqual((Stride.nanoseconds(-12) - .nanoseconds(7)).magnitude,
|
||||
-19000000000)
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(0)
|
||||
stride -= .microseconds(2)
|
||||
XCTAssertEqual(stride.magnitude, -2000000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(2)
|
||||
stride -= .microseconds(0)
|
||||
XCTAssertEqual(stride.magnitude, 2000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride -= .nanoseconds(12)
|
||||
XCTAssertEqual(stride.magnitude, -5000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(12)
|
||||
stride -= .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, 5000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(7)
|
||||
stride -= .nanoseconds(-12)
|
||||
XCTAssertEqual(stride.magnitude, 19000000000)
|
||||
}
|
||||
|
||||
do {
|
||||
var stride = Stride.nanoseconds(-12)
|
||||
stride -= .nanoseconds(7)
|
||||
XCTAssertEqual(stride.magnitude, -19000000000)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrideCodable() throws {
|
||||
typealias Stride = Scheduler.SchedulerTimeType.Stride
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let stride = Stride.nanoseconds(419872)
|
||||
let encodedData = try encoder
|
||||
.encode(KeyedWrapper(value: stride))
|
||||
let encodedString = String(decoding: encodedData, as: UTF8.self)
|
||||
|
||||
XCTAssertEqual(encodedString, #"{"value":{"magnitude":419872}}"#)
|
||||
|
||||
let decodedStride = try decoder
|
||||
.decode(KeyedWrapper<Stride>.self, from: encodedData)
|
||||
.value
|
||||
|
||||
XCTAssertEqual(decodedStride, stride)
|
||||
}
|
||||
|
||||
// MARK: - Scheduler
|
||||
|
||||
func testMinimumTolerance() {
|
||||
XCTAssertEqual(mainScheduler.minimumTolerance, .nanoseconds(0))
|
||||
XCTAssertEqual(backgroundScheduler.minimumTolerance, .nanoseconds(0))
|
||||
}
|
||||
|
||||
func testNow() {
|
||||
let expectedNow = DispatchTime.now().uptimeNanoseconds
|
||||
let actualNowMainScheduler = mainScheduler
|
||||
.now
|
||||
.dispatchTime
|
||||
.uptimeNanoseconds
|
||||
let actualNowBackgroundScheduler = backgroundScheduler
|
||||
.now
|
||||
.dispatchTime
|
||||
.uptimeNanoseconds
|
||||
XCTAssertLessThan(abs(actualNowMainScheduler.distance(to: expectedNow)),
|
||||
1_000_000/*nanoseconds*/)
|
||||
XCTAssertLessThan(abs(actualNowBackgroundScheduler.distance(to: expectedNow)),
|
||||
1_000_000/*nanoseconds*/)
|
||||
}
|
||||
|
||||
func testDefaultSchedulerOptions() {
|
||||
let options = Scheduler.SchedulerOptions()
|
||||
XCTAssertEqual(options.flags, [])
|
||||
XCTAssertEqual(options.qos, .unspecified)
|
||||
XCTAssertNil(options.group)
|
||||
}
|
||||
|
||||
func testScheduleActionOnceNow() {
|
||||
let main = expectation(description: "scheduled on main queue")
|
||||
main.assertForOverFulfill = true
|
||||
|
||||
var didExecuteMainAction = false
|
||||
let didExecuteBackgroundAction = Atomic(false)
|
||||
|
||||
mainScheduler.schedule {
|
||||
didExecuteMainAction = true
|
||||
main.fulfill()
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
backgroundScheduler
|
||||
.schedule(options: .init(qos: .userInteractive, group: group)) {
|
||||
didExecuteBackgroundAction.do { $0 = true }
|
||||
}
|
||||
|
||||
XCTAssertFalse(didExecuteMainAction, "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")
|
||||
XCTAssertTrue(didExecuteBackgroundAction.value)
|
||||
|
||||
wait(for: [main], timeout: 0.1)
|
||||
}
|
||||
|
||||
func testScheduleActionOnceLater() {
|
||||
|
||||
let main = expectation(description: "scheduled on main queue")
|
||||
main.assertForOverFulfill = true
|
||||
|
||||
var didExecuteAction = false
|
||||
|
||||
let delay = Scheduler.SchedulerTimeType.Stride.milliseconds(200)
|
||||
|
||||
mainScheduler.schedule(after: mainScheduler.now.advanced(by: delay)) {
|
||||
didExecuteAction = true
|
||||
main.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertFalse(didExecuteAction, "action should be executed asynchronously")
|
||||
|
||||
wait(for: [main], timeout: 3/*seconds*/)
|
||||
}
|
||||
|
||||
func testScheduleRepeating() {
|
||||
let main = expectation(description: "scheduled on main queue")
|
||||
main.expectedFulfillmentCount = 4
|
||||
main.assertForOverFulfill = true
|
||||
|
||||
let delay = Scheduler.SchedulerTimeType.Stride.milliseconds(100)
|
||||
let interval = Scheduler.SchedulerTimeType.Stride.milliseconds(50)
|
||||
|
||||
var didExecuteAction = false
|
||||
|
||||
let token = mainScheduler
|
||||
.schedule(after: mainScheduler.now.advanced(by: delay),
|
||||
interval: interval) {
|
||||
didExecuteAction = true
|
||||
main.fulfill()
|
||||
}
|
||||
|
||||
XCTAssert(token is AnyCancellable)
|
||||
XCTAssertFalse(didExecuteAction, "action should be executed asynchronously")
|
||||
|
||||
wait(for: [main], timeout: 3/*seconds*/)
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private typealias Scheduler = DispatchQueue
|
||||
|
||||
private let mainScheduler = DispatchQueue.main
|
||||
private let backgroundScheduler = DispatchQueue.global(qos: .background)
|
||||
|
||||
#else
|
||||
|
||||
private typealias Scheduler = DispatchQueue.OCombine
|
||||
|
||||
private let mainScheduler = DispatchQueue.main.ocombine
|
||||
private let backgroundScheduler = DispatchQueue.global(qos: .background).ocombine
|
||||
|
||||
#endif
|
||||
|
||||
private struct KeyedWrapper<Value: Codable & Equatable>: Codable, Equatable {
|
||||
let value: Value
|
||||
}
|
||||
@@ -5,12 +5,13 @@
|
||||
// Created by Joseph Spadafora on 6/29/19.
|
||||
//
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
#if !OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Foundation
|
||||
import OpenCombine
|
||||
|
||||
extension JSONDecoder: TopLevelDecoder {}
|
||||
extension JSONEncoder: TopLevelEncoder {}
|
||||
|
||||
extension PropertyListDecoder: TopLevelDecoder {}
|
||||
extension PropertyListEncoder: TopLevelEncoder {}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// AssertCrashes.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 31.07.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
extension XCTest {
|
||||
|
||||
var testcaseName: String {
|
||||
return String(describing: type(of: self))
|
||||
}
|
||||
|
||||
var testName: String {
|
||||
// Since on Apple platforms `self.name` has
|
||||
// format `-[XCTestCaseSubclassName testMethodName]`,
|
||||
// and on other platforms the format is
|
||||
// `XCTestCaseSubclassName.testMethodName`
|
||||
// we have this workaround in order to unify the names
|
||||
return name
|
||||
.components(separatedBy: testcaseName)
|
||||
.last!
|
||||
.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
}
|
||||
|
||||
// Taken from swift-corelibs-foundation and slightly modified for OpenCombine
|
||||
@available(macOS 10.13, iOS 8.0, *)
|
||||
func assertCrashes(within block: () throws -> Void) rethrows {
|
||||
#if !Xcode && !os(iOS) && !os(watchOS) && !os(tvOS)
|
||||
let childProcessEnvVariable = "OPENCOMBINE_TEST_PERFORM_ASSERT_CRASHES_BLOCKS"
|
||||
let childProcessEnvVariableOnValue = "YES"
|
||||
|
||||
let isChildProcess = ProcessInfo
|
||||
.processInfo
|
||||
.environment[childProcessEnvVariable] == childProcessEnvVariableOnValue
|
||||
|
||||
if isChildProcess {
|
||||
try block()
|
||||
} else {
|
||||
var arguments = ProcessInfo.processInfo.arguments
|
||||
let xctestUtilityPath = URL(fileURLWithPath: arguments[0])
|
||||
|
||||
let childProcess = Process()
|
||||
childProcess.executableURL = xctestUtilityPath
|
||||
|
||||
arguments.removeFirst()
|
||||
arguments.removeAll { $0.hasPrefix("OpenCombineTests.") || $0 == "-XCTest" }
|
||||
arguments.insert("OpenCombineTests.\(testcaseName)/\(testName)", at: 0)
|
||||
#if os(macOS)
|
||||
arguments.insert("-XCTest", at: 0)
|
||||
#endif
|
||||
childProcess.arguments = arguments
|
||||
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
environment[childProcessEnvVariable] = childProcessEnvVariableOnValue
|
||||
childProcess.environment = environment
|
||||
|
||||
func printDiagostics() {
|
||||
print("Parent process invocation:")
|
||||
print(ProcessInfo.processInfo.arguments.joined(separator: " "))
|
||||
print("Child process invocation:")
|
||||
print(
|
||||
([ProcessInfo.processInfo.arguments[0]] + arguments)
|
||||
.joined(separator: " ")
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
try childProcess.run()
|
||||
childProcess.waitUntilExit()
|
||||
if childProcess.terminationReason != .uncaughtSignal {
|
||||
XCTFail("Child process should have crashed: \(childProcess)")
|
||||
printDiagostics()
|
||||
}
|
||||
} catch {
|
||||
XCTFail("""
|
||||
Couldn't start child process for testing crash: \(childProcess) - \(error)
|
||||
""")
|
||||
printDiagostics()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// CleaningUpSubscriber.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.10.2019.
|
||||
//
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CleaningUpSubscriber<Input, Failure: Error>: Subscriber {
|
||||
|
||||
private(set) var subscription: Subscription?
|
||||
|
||||
private let onDeinit: () -> Void
|
||||
|
||||
init(onDeinit: @escaping () -> Void) {
|
||||
self.onDeinit = onDeinit
|
||||
}
|
||||
|
||||
deinit {
|
||||
onDeinit()
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
self.subscription = subscription
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
subscription = nil
|
||||
}
|
||||
}
|
||||
@@ -30,34 +30,72 @@ import OpenCombine
|
||||
/// publisher.subscribe(subscriber)
|
||||
///
|
||||
/// assert(subscription.history == [.requested(.max(42)), .cancelled])
|
||||
@available(macOS 10.15, *)
|
||||
typealias CustomPublisher = CustomPublisherBase<Int>
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
typealias CustomPublisher = CustomPublisherBase<Int, TestingError>
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class CustomPublisherBase<Value: Equatable>: Publisher {
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
class CustomPublisherBase<Output: Equatable, Failure: Error>: Publisher {
|
||||
|
||||
typealias Output = Value
|
||||
typealias Failure = TestingError
|
||||
|
||||
private var subscriber: AnySubscriber<Value, TestingError>?
|
||||
private(set) var subscriber: AnySubscriber<Output, Failure>?
|
||||
private(set) var erasedSubscriber: Any?
|
||||
private let subscription: Subscription?
|
||||
|
||||
init(subscription: Subscription?) {
|
||||
required init(subscription: Subscription?) {
|
||||
self.subscription = subscription
|
||||
}
|
||||
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
self.subscriber = AnySubscriber(subscriber)
|
||||
erasedSubscriber = subscriber
|
||||
subscription.map(subscriber.receive(subscription:))
|
||||
}
|
||||
|
||||
func send(_ value: Value) -> Subscribers.Demand {
|
||||
return subscriber!.receive(value)
|
||||
func send(_ value: Output) -> Subscribers.Demand {
|
||||
return subscriber?.receive(value) ?? .none
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<TestingError>) {
|
||||
func send(completion: Subscribers.Completion<Failure>) {
|
||||
subscriber!.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
typealias CustomConnectablePublisher = CustomConnectablePublisherBase<Int, TestingError>
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CustomConnectablePublisherBase<Output: Equatable, Failure: Error>
|
||||
: CustomPublisherBase<Output, Failure>,
|
||||
ConnectablePublisher
|
||||
{
|
||||
|
||||
enum Event: CustomStringConvertible {
|
||||
case connected, disconnected
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .connected:
|
||||
return ".connected"
|
||||
case .disconnected:
|
||||
return ".disconnected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Connection: Cancellable {
|
||||
|
||||
let onCancel: () -> Void
|
||||
|
||||
func cancel() {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var connectionHistory: [Event] = []
|
||||
|
||||
func connect() -> Cancellable {
|
||||
connectionHistory.append(.connected)
|
||||
return Connection { self.connectionHistory.append(.disconnected) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import OpenCombine
|
||||
///
|
||||
/// In order to inject `CustomSubscription` into the chain of subscriptions,
|
||||
/// use the `CustomSubscriber` class.
|
||||
@available(macOS 10.15, *)
|
||||
final class CustomSubscription: Subscription {
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CustomSubscription: Subscription, CustomStringConvertible {
|
||||
|
||||
enum Event: Equatable, CustomStringConvertible {
|
||||
case requested(Subscribers.Demand)
|
||||
@@ -26,9 +26,9 @@ final class CustomSubscription: Subscription {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .requested(let demand):
|
||||
return "requested(\(demand))"
|
||||
return ".requested(.\(demand))"
|
||||
case .cancelled:
|
||||
return "cancelled"
|
||||
return ".cancelled"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,12 @@ final class CustomSubscription: Subscription {
|
||||
private(set) var history: [Event] = []
|
||||
|
||||
private let _requested: ((Subscribers.Demand) -> Void)?
|
||||
private let _canceled: (() -> Void)?
|
||||
private let _cancelled: (() -> Void)?
|
||||
|
||||
init(onRequest: ((Subscribers.Demand) -> Void)? = nil,
|
||||
onCancel: (() -> Void)? = nil) {
|
||||
_requested = onRequest
|
||||
_canceled = onCancel
|
||||
_cancelled = onCancel
|
||||
}
|
||||
|
||||
var lastRequested: Subscribers.Demand? {
|
||||
@@ -56,7 +56,7 @@ final class CustomSubscription: Subscription {
|
||||
}.last
|
||||
}
|
||||
|
||||
var canceled = false
|
||||
var cancelled = false
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
history.append(.requested(demand))
|
||||
@@ -65,7 +65,9 @@ final class CustomSubscription: Subscription {
|
||||
|
||||
func cancel() {
|
||||
history.append(.cancelled)
|
||||
canceled = true
|
||||
_canceled?()
|
||||
cancelled = true
|
||||
_cancelled?()
|
||||
}
|
||||
|
||||
var description: String { return "CustomSubscription" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// OperatorTestHelper.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/6/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
/// `OperatorTestHelper` is an abstraction that helps avoid a lot of boilerplate when
|
||||
/// testing an operator. It is initialized with a publisher type and creates a
|
||||
/// `CustomSubscription`, `CustomPublisherBase` and `TrackingSubscriberBase`.
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
class OperatorTestHelper<SourceValue: Equatable,
|
||||
SourceError: Error,
|
||||
SourcePublisher,
|
||||
Sut: Publisher>
|
||||
where SourcePublisher: CustomPublisherBase<SourceValue, SourceError>
|
||||
{
|
||||
typealias Value = Sut.Output
|
||||
typealias Failure = Sut.Failure
|
||||
|
||||
let subscription: CustomSubscription
|
||||
let publisher: SourcePublisher
|
||||
let tracking: TrackingSubscriberBase<Value, Failure>
|
||||
private(set) var sut: Sut
|
||||
|
||||
var downstreamSubscription: Subscription?
|
||||
|
||||
/// This initializes the `OperatorTestHelper`. In most cases,
|
||||
/// you can just pass a `publisherType` and closure
|
||||
/// for `createSut` to get all the setup that you'll need for a test.
|
||||
/// - Parameter publisherType: This should be filled in with the
|
||||
/// type of `CustomPublisherBase` that you would like the
|
||||
/// operator you are testing to be built from.
|
||||
/// - Parameter initialDemand: This is the demand that the
|
||||
/// created `TrackingSubscriber` should return upon receiving a subscription.
|
||||
/// - Parameter receiveValueDemand: This is the demand that the
|
||||
/// created `TrackingSubscriber should return upon receiving a value.
|
||||
/// - Parameter customSubscription: This parameter defaults to `CustomSubscription()`,
|
||||
/// but can be replaced with your own instance if you want to override
|
||||
/// any of the default `CustomSubscription` initializer closures.
|
||||
/// - Parameter createSut: This closure takes a new concrete instance
|
||||
/// of the `publisherType` as an input to the closure and creates an
|
||||
/// instance of the operator that you are trying to test.
|
||||
init(publisherType: SourcePublisher.Type,
|
||||
initialDemand: Subscribers.Demand?,
|
||||
receiveValueDemand: Subscribers.Demand,
|
||||
customSubscription: CustomSubscription = CustomSubscription(),
|
||||
createSut: (SourcePublisher) -> Sut)
|
||||
{
|
||||
self.subscription = customSubscription
|
||||
let createdPublisher = publisherType.init(subscription: customSubscription)
|
||||
self.publisher = createdPublisher
|
||||
self.sut = createSut(createdPublisher)
|
||||
self.tracking = TrackingSubscriberBase<Value, Failure>(
|
||||
receiveSubscription: {
|
||||
initialDemand.map($0.request)
|
||||
},
|
||||
receiveValue: { _ in receiveValueDemand }
|
||||
)
|
||||
tracking.onSubscribe = { self.downstreamSubscription = $0 }
|
||||
sut.subscribe(tracking)
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,9 @@ class PerformanceTestCase: GottaGoFast.PerformanceTestCase {
|
||||
block)
|
||||
#endif
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
func blackHole<Value>(_: Value) {}
|
||||
}
|
||||
|
||||
extension XCTestCase {
|
||||
|
||||
@@ -11,18 +11,18 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
class TestEncoder: TopLevelEncoder {
|
||||
final class TestEncoder: TopLevelEncoder {
|
||||
typealias Output = Int
|
||||
|
||||
private var nextEncoded = 1
|
||||
|
||||
var encoded: [Int: Any] = [:]
|
||||
var encoded: [Int : Any] = [:]
|
||||
|
||||
var handleEncode: ((Any) -> Int?)?
|
||||
var handleEncode: ((Any) throws -> Int?)?
|
||||
|
||||
func encode<EncodableType: Encodable>(_ value: EncodableType) throws -> Int {
|
||||
var keyNumber = nextEncoded
|
||||
if let number = handleEncode?(value) {
|
||||
if let number = try handleEncode?(value) {
|
||||
keyNumber = number
|
||||
} else {
|
||||
nextEncoded += 1
|
||||
@@ -32,19 +32,18 @@ class TestEncoder: TopLevelEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
class TestDecoder: TopLevelDecoder {
|
||||
final class TestDecoder: TopLevelDecoder {
|
||||
typealias Input = Int
|
||||
|
||||
static let error = "Could not decode" as TestingError
|
||||
|
||||
var handleDecode: ((Int) -> Any?)?
|
||||
var handleDecode: ((Int) throws -> Any?)?
|
||||
|
||||
func decode<DecodablyType: Decodable>(
|
||||
_ type: DecodablyType.Type,
|
||||
from data: Int)
|
||||
throws -> DecodablyType
|
||||
{
|
||||
if let value = handleDecode?(data), let mappedValue = value as? DecodablyType {
|
||||
func decode<Decodable: Swift.Decodable>(
|
||||
_ type: Decodable.Type,
|
||||
from data: Int
|
||||
) throws -> Decodable {
|
||||
if let value = try handleDecode?(data), let mappedValue = value as? Decodable {
|
||||
return mappedValue
|
||||
}
|
||||
throw TestDecoder.error
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user