172 Commits

Author SHA1 Message Date
Sergej Jaskiewicz 26e86a9905 Implement CombineLatest 2021-06-21 19:09:04 +03:00
Sergej Jaskiewicz bab8e08d2f Work around SwiftLint nested configuration bug
There is a bug introduced in SwiftLint 0.43.0 (?) when nested configurations don't work.
Nested configurations let us place additional .swiftlint.yml files in subdirectories that
specify rules that should only apply to that subdirectory. This is broken now.
2021-06-21 17:38:33 +03:00
Sergej Jaskiewicz 4060ee9f57 Fix compatibility with Xcode 12.5 toolchain and SDKs 2021-06-21 17:38:33 +03:00
Sergej Jaskiewicz 5996772433 Bump Xcode version for compatibility testing 2021-02-22 20:47:35 +03:00
Sergej Jaskiewicz cd45c77fac Implement Publishers.PrefixUntilOutput 2021-02-22 20:47:35 +03:00
Stuart Austin e618d179fe Add Publishers.Throttle implementation (#195)
* Publishers.Throttle implementation with tests

* Fix Throttle lint errors and removed expectation from throttle tests. Add additional test for cancelling a subscription before a scheduled value is emitted

* Fix VirtualTimeScheduler's executeSchedulesActions default deadline not being far enough into the future on 32-bit platforms.

* Fixed multiple lint errors

* Improve Publishers.Throttle code coverage by removing enum for pending emissions

* Additional Throttle test for cancelling a Subscriber when an output has been scheduled

* ThrottleTests now run on WASI
2021-02-18 13:56:55 +00:00
Marcus Scherer 4fa5f48c19 Fix typo (#204) 2021-02-08 19:41:49 +03:00
Max Desiatov 28993ae57d Add CHANGELOG.md, bump version to 0.12.0 (#202)
* Add CHANGELOG.md, bump version to 0.12.0

* Mention the timer bug fix in `CHANGELOG.md`
2021-01-29 15:05:56 +00:00
Grigory Entin 3d61bf87e7 Fixed Timer(timeInterval:,repeats:,block:) not accounting timeInterval for the first fire date. (#196)
https://github.com/OpenCombine/OpenCombine/blob/master/Sources/OpenCombineFoundation/Helpers/Portability.swift#L58-L64

It looks like this was a typo/something overlooked, but basically, this `fire: Date()` breaks at least every timer publisher like `Timer.publish(every: timeInterval, on: .main, in: .default)`, as it basically results in the *first* event fired immediately vs in timeInterval. (Just in case, no, Combine does not fire that extra event).

* Fixed Timer(timeInterval:,repeats:,block:) not accounting timeInterval for the first fire date.

* Fixed Danger warning about line length.
2021-01-29 13:42:17 +00:00
Max Desiatov 911a4e1aa3 Add OpenCombineShim product for easier importing (#197) 2021-01-25 17:25:28 +03:00
Yuta Saito beb38dec0e Implementation for ObservableObject with Mirror (#201)
A temporary implementation until we implement the proper type metadata introspection.
2021-01-25 17:24:19 +03:00
Nomo Nomad 1fbf688897 Update README.md (#199) 2020-12-11 16:41:20 +03:00
Sergej Jaskiewicz 5436868053 Fix some lock acquiring in Publishers.FlatMap (#194) 2020-11-08 17:44:33 +03:00
Sergej Jaskiewicz 4977ca158f Update DispatchQueue scheduler to match iOS 14.2 behavior 2020-11-07 17:28:08 +03:00
Sergej Jaskiewicz 96214ac5f9 Run compatibility tests on iOS 14.2 2020-11-07 17:28:08 +03:00
Sergej Jaskiewicz 21fda909f5 Implement Publishers.Retry 2020-11-07 17:28:08 +03:00
Sergej Jaskiewicz 8438d09b82 Increase time intervals in OperationQueue tests
The test is sporadically failing on iOS 9.3.
2020-11-03 17:21:34 +03:00
Sergej Jaskiewicz 30a60b52cc Add missing availability annotations in tests
Fixes #192
2020-11-03 17:21:34 +03:00
Sergej Jaskiewicz a93ed143fb Add more supported platforms to Package.swift 2020-11-03 17:21:34 +03:00
Max Desiatov e054a884ef Add support for SwiftWasm with CI and tests (#191)
WebAssembly support for atomics and multi-threading isn't fully standardized yet, and it not supported in SwiftWasm at the moment. Because of this Dispatch is unavailable, and all Combine-related Foundation stuff is unavailable too. Tests related to this are disabled. Locking functions are replaced with no-op shims.
2020-11-02 22:02:39 +00:00
Sergej Jaskiewicz 9bba508134 Bump the version to 0.11.0 2020-10-30 00:53:48 +03:00
Sergej Jaskiewicz 2d857d6d66 Remove the note about the project being in early development 2020-10-30 00:53:48 +03:00
Sergej Jaskiewicz 7286336b28 Enable URLSession tests on non-Darwin platforms since Swift 5.3
Closes #169.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 8a0bb6f846 [Xcode 12] Update Publishers.Debounce implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 142811c500 [Xcode 12] Update @Published implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 3b1cff9337 [Xcode 12] Implement Optional.publisher property 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz efb4369c74 [Xcode 12] Add new flatMap(maxPublishers:_:) overloads 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 9d87a3b4ea [Xcode 12] Update Publishers.Buffer implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 4714b80631 [Xcode 12] Update Publishers.Drop implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 3cba7363b4 Better API for the Atomic test helper 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz b2f592611d Enable tests on Ubuntu using Swift 5.0 and make them pass 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 8786d0860a Use custom script for test discovery
Standard test discovery is not available in Swift 5.0, but we want to
support this version of Swift.

Besides, standard test discovery doesn't work very well with CircleCI:
https://bugs.swift.org/browse/SR-10783
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 9100ccafb3 Enable testing with Swift 5.1 and Swift 5.3 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 3fa048ddca Make DispatchQueue scheduler tests pass in release mode.
In the new version of the Swift compiler, due to a new optimization
the cancellation token was deallocated immediately, resulting in
the closure never being executed. Fix this by extending the token's
lifetime.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 8832baa05b [Xcode 12] Update Publishers.ReplaceError implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 11fdf7eaf3 [Xcode 12] Update Publishers.[Encode|Decode] implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 059a86d393 [Xcode 12] Update Publishers.CompactMap implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz c1b4d93a0f [Xcode 12] Update Publishers.Filter implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz fb184ceebe [Xcode 12] Add new switchToLatest() overloads 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 69b50074ff [Xcode 12] Update AnyPublisher and AnySubscriber initializers
The new version avoids double boxing.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 7351cd671c [Xcode 12] Update documentation 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 26e112f894 [Xcode 12] Implement _Introspection functionality
_Introspection allows to observe the subscription graph and data flow.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 6c732515d8 [Xcode 12] Update Publishers.Delay implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 6892923743 [Xcode 12] Update Timer publisher implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz f8809ffac4 [Xcode 12] Update Publishers.HandleEvents implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 5da402bb2c Fix a deprecation warning in Publishers.Breakpoint implementation 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz e962ce1e3b [Xcode 12] Update OperationQueue scheduler implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 19df744bf1 [Xcode 12] Update Publishers.ReceiveOn implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 69cc5a92c2 [Xcode 12] Update Publishers.Timeout implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz cb22cc98f2 [Xcode 12] Update Publishers.Sequence implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 22cf5b69ba [Xcode 12] Update Publishers.SubscribeOn implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz a3fecd18e6 [Xcode 12] Update RunLoop scheduler implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 48c6f2999b [Xcode 12] Update Publishers.MeasureInterval implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 91297ae63a [Xcode 12] Update Publishers.IgnoreOutput implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 6f61dcc083 [Xcode 12] Update Subscribers.Assign implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 24eca2fab8 [Xcode 12] Update Publishers.Breakpoint implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 9b3c36124f [Xcode 12] Update Subscribers.Sink implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 1b017e1dfc [Xcode 12] Update DispatchQueue.SchedulerTimeType.Stride implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 32b3aeb94c [Xcode 12] Update CombineIdentifier implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 579d174288 [Xcode 12] Update Publishers.Concatenate implementation and tests
The tests now pass in compatibility mode when run against iOS 14 Combine.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 1c850fc6bb Make it buildable with Xcode 12 toolchain 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz b35ecf8356 Enable compatibility testing against iOS 14 Combine 2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz 302f663a3f Don't track file and line in test methods
Since Xcode 12 those are tracked automatically.
No need to use #file and #line tokens in method arguments.
2020-10-29 19:49:34 +03:00
Sergej Jaskiewicz c805f0f5aa Bump the version to 0.10.2 (#188) 2020-10-23 15:29:02 +03:00
Sergej Jaskiewicz cb99f8b298 Make downstreamSubject a weak property in SubjectSubscriber (#187)
This is how it is in Combine.

Fixes #186.
2020-10-23 13:48:43 +03:00
Maximilian Wendel 915a7efaf5 Prepare for 0.10.1 (#185) 2020-10-04 15:01:09 +03:00
Sergej Jaskiewicz 024e576b0f Add link to generated interfaces for Combine 2020-10-01 13:21:33 +03:00
Max Desiatov f4a611e95f Run compatibility tests on iOS 13.6/Xcode 11.6 (#181) 2020-08-31 12:12:38 +01:00
Maximilian Wendel c09e47f792 Fix OperationQueue scheduler on non-Darwin platforms before Swift 5.1 (#177) 2020-07-29 16:26:50 +03:00
Maximilian Wendel dd6be33016 Don't use PropertyListEncoder on non-Darwin platforms before Swift 5.1 (#176)
PropertyListEncoder and PropertyListDecoder are both unavailable prior to Swift 5.1, causing a build error for Swift 5.0.
2020-07-29 16:24:28 +03:00
dependabot[bot] 5af4fb6ba4 Bump json from 2.2.0 to 2.3.1 (#175) 2020-07-28 07:22:03 +00:00
Adam Leonard 0ca4c7658f Fix a build error on linux: kCFStringEncodingUTF8 is not defined. (#173)
Instead, use `CFStringBuiltInEncodings.UTF8.rawValue`.

Also fix a type error I was getting in a unit test.

Co-authored-by: adaml <adam@seesaw.me>
2020-07-21 16:05:48 +03:00
Alexey Salangin 8cf59d6d2a Fix some typos (#172) 2020-07-14 08:48:35 +03:00
Sergej Jaskiewicz f3d068d6f2 Bump the version to 0.10.0 (#171) 2020-06-28 20:39:03 +03:00
Sergej Jaskiewicz 1cfb4a2eae Implement Publishers.Debounce (#133) 2020-06-28 19:50:45 +03:00
Sergej Jaskiewicz 2b64b7981d Implement Publishers.Timeout (#164) 2020-06-28 14:31:15 +03:00
Sergej Jaskiewicz ad95dfdc8c Update CircleCI badge 2020-06-26 17:25:12 +03:00
Sergej Jaskiewicz 988644159e Update badges after migrating to an organization 2020-06-26 16:32:22 +03:00
Sergej Jaskiewicz a9fa1ed4f4 Update the repository URL after migrating to an organization 2020-06-26 16:19:42 +03:00
Sergej Jaskiewicz 3f125b30e1 Implement OperationQueue scheduler (#165) 2020-06-26 15:40:15 +03:00
Sergej Jaskiewicz c9e7293a2a Fix behavior of CurrentValueSubject when setting new value after completion 2020-06-26 11:38:57 +03:00
Sergej Jaskiewicz f5d2c39c58 Add a test for CurrentValueSUbject 2020-06-26 11:17:32 +03:00
Sergej Jaskiewicz 70bf8e8bb3 Run compatibility tests on iOS 13.5/Xcode 11.5 2020-06-26 00:11:34 +03:00
Sergej Jaskiewicz f04053e1eb A more efficient and correct implementation of Future 2020-06-26 00:11:34 +03:00
Sergej Jaskiewicz af510706d7 A more efficient and correct implementation of CurrentValueSubject 2020-06-26 00:11:34 +03:00
Sergej Jaskiewicz 29fbf7de31 A more efficient and correct implementation of PassthroughSubject 2020-06-26 00:11:34 +03:00
Sergej Jaskiewicz 102eef88a0 Implement ConduitList 2020-06-26 00:11:34 +03:00
Sergej Jaskiewicz b34d4652d3 Make TimerPublisher tests more stable (#167) 2020-06-24 16:09:15 +03:00
Max Desiatov fcc2a4350a Add TimerPublisher and Timer.publish (#156)
Co-authored-by: Sergej Jaskiewicz <jaskiewiczs@icloud.com>
2020-06-23 20:55:20 +03:00
Sergej Jaskiewicz 59183ce0a5 Document the release process 2020-06-12 23:54:21 +03:00
Sergej Jaskiewicz b1f676d273 Bump the version to 0.9.0 2020-06-12 23:54:21 +03:00
Sergej Jaskiewicz b2784a1011 Implement Publishers.Catch and Publishers.TryCatch (#140) 2020-06-11 22:17:16 +03:00
Max Desiatov d67e77c84d Test with Swift 5.2 on Ubuntu 18.04 (#159)
I don't think it makes much sense to test on an older version of Swift on Ubuntu. Since we tested only a single version, I've updated that to the latest available, but let me know if you'd like to test with multiple Swift versions on Linux.

As a sidenote, I hope we could also switch to GitHub Actions in the future. Circle CI seems to be annoyingly slow.
2020-06-06 19:43:20 +01:00
Vuk Radosavljevic d680f09932 Change collection to set in documentation (#151) 2020-04-10 10:16:26 +01:00
Sergej Jaskiewicz 30b5dd4c2f Update for Xcode 11.4 release (#150) 2020-03-28 21:23:57 +03:00
Sergej Jaskiewicz 621f970998 Update to match the behavior in Xcode 11.4 beta 2 SDKs. (#148) 2020-02-26 13:56:18 +03:00
Sergej Jaskiewicz d6b70ad309 Implement the RunLoop scheduler (#131) 2020-02-05 02:11:10 +03:00
Sergej Jaskiewicz 918e9131ad Implement Publishers.SwitchToLatest (#142) 2020-02-04 13:26:17 +03:00
Rob Mayoff 7f7f397062 Add opencombine_lldb.py for better Demand formatting in lldb/Xcode (#146) 2020-01-29 14:57:44 +03:00
Rob Mayoff 3b1437e46c Work around SR-11680 (#145)
The Swift bug report: https://bugs.swift.org/browse/SR-11680

Swift nightly toolchains are available here: https://swift.org/download/

The Swift nightly toolchains cannot build OpenCombine. Here's why:

The COpenCombineHelpers target defines a non-static function
(`opencombine_stop_in_debugger`) in a header file. This function is
emitted in the target's IR, but not in the target's TBD.

Swift nightly toolchains have assertions enabled, so they use the
-validate-tbd-against-ir=missing build setting. This build setting
makes the compiler fail if the TBD doesn't match the IR.

This commit un-inlines `opencombine_stop_in_debugger`, so it
is not emitted in the IR. This stops the TBD validator from
complaining.
2020-01-24 02:17:40 +03:00
Sergej Jaskiewicz 79899f7742 Bump podspec version for OpenCombineFoundation 2020-01-17 17:25:10 +03:00
Sergej Jaskiewicz 1496bab272 Prepare for 0.8.0 2020-01-17 17:15:39 +03:00
Sergej Jaskiewicz 1ebbdb8ea9 Implement Publishers.Buffer (#143) 2020-01-17 11:01:43 +03:00
Sergej Jaskiewicz f861335dc3 Implement Publishers.AssertNoFailure (#138) 2019-12-25 19:15:57 +03:00
Sergej Jaskiewicz 769c3c818f Implement Publishers.CollectByCount (#137) 2019-12-25 03:01:34 +03:00
Sergej Jaskiewicz 910d21da4c Implement Publishers.DropUntilOutput (#136) 2019-12-24 22:14:10 +03:00
Sergej Jaskiewicz 6e20956d6d Guess unknown DisaptchTimeInterval more precisely (#135) 2019-12-24 19:18:29 +03:00
dependabot[bot] e453879d75 Bump excon from 0.68.0 to 0.71.0 (#132)
Bumps [excon](https://github.com/excon/excon) from 0.68.0 to 0.71.0.
- [Release notes](https://github.com/excon/excon/releases)
- [Changelog](https://github.com/excon/excon/blob/master/changelog.txt)
- [Commits](https://github.com/excon/excon/compare/v0.68.0...v0.71.0)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-17 02:25:13 +03:00
Sergej Jaskiewicz 98f6b6b337 Fix more overflows in DispatchQueue.SchedulerTimeType (#130) 2019-12-15 15:57:05 +03:00
Sergej Jaskiewicz 74b739d74e Work around the 'default will never be executed' warning on Linux (#129)
Also, enable the -warnings-as-errors flag on CI.
2019-12-15 02:37:33 +03:00
Sergej Jaskiewicz bcba9a19d4 Update for Xcode 11.3 (#123)
- Send subscription synchronously in ReceiveOn and Delay operators
- Some locks made recursive, as they should be
- ObservableObjectPublisher doesn't use PassthroughSubject under the hood anymore
2019-12-14 23:11:47 +03:00
Max Desiatov 486e166462 Expose OpenCombineFoundation target as a product (#128)
This should fix `import OpenCombineFoundation` issue reported in #124.
2019-12-14 00:02:03 +00:00
Sergej Jaskiewicz c6536cf8d3 Implement URLSession.DataTaskPublisher (#127) 2019-12-13 16:44:03 +03:00
Sergej Jaskiewicz cf41c25cf7 Implement NotificationCenter.Publisher 2019-12-13 10:34:47 +03:00
Sergej Jaskiewicz b4557fb311 Create OpenCombineFoundation target
Implement TopLevelEncoder/TopLevelDecoder conformances for:

- JSONEncoder
- JSONDecoder
- PropertyListEncoder
- PropertyListDecoder
2019-12-13 10:34:47 +03:00
Sergej Jaskiewicz f8e6e66ab4 Fix integer overflows in DispatchQueue.SchedulerTimeType.Stride (#126) 2019-12-12 23:45:01 +03:00
Joe Spadafora 95b42abce3 Implement Publishers.ReplaceEmpty (#122) 2019-12-11 19:34:24 +03:00
Sergej Jaskiewicz 899a04bb3f Bump version to 0.7.0 2019-12-11 02:43:17 +03:00
Sergej Jaskiewicz 5f9a700689 Implement Publishers.Concatenate (#90) 2019-12-10 13:37:44 +03:00
Sergej Jaskiewicz a300fd09d3 [CocoaPods] Make COpenCombineHelpers part of the OpenCombine pod
CocoaPods doesn't support multiple Swift modules in the same pod.
Build COpenCombineHelpers sources together with OpenCombine sources
as a single module.

Previously COpenCombineHelpers was a separate pod. This was suboptimal,
as it made making changes in both targets very hard: you'd have to
push COpenCombineHelpers to trunk in order to pass validation.
2019-12-09 15:18:53 +03:00
Sergej Jaskiewicz 5973f86c6e Implement Publishers.HandleEvents 2019-12-09 15:18:53 +03:00
Sergej Jaskiewicz 1b5afdba26 Implement Publishers.Breakpoint 2019-12-09 15:18:53 +03:00
Sergej Jaskiewicz 51d5d1e71d Implement MeasureInterval (#117) 2019-12-03 14:26:00 +03:00
Sergej Jaskiewicz a8bc5cc046 Implement SubscribeOn (#116) 2019-12-03 12:11:31 +03:00
Sergej Jaskiewicz 86d6170dc9 Implement ReceiveOn (#115) 2019-12-02 20:30:58 +03:00
Sergej Jaskiewicz 171131d768 Implement Delay (#114) 2019-12-02 18:18:46 +03:00
Sergej Jaskiewicz d6b4fb4115 Bump COpenCombineHelpers.podspec version 2019-11-26 19:21:18 +03:00
Sergej Jaskiewicz 014b82b99d Bump version (#113) 2019-11-26 19:02:02 +03:00
Sergej Jaskiewicz 7c5a76cf2b Fix @Published (#112) 2019-11-26 17:46:01 +03:00
Max Desiatov 668c292245 Add more tests for the Future publisher (#111) 2019-11-26 14:23:51 +03:00
Max Desiatov 981ab4fa09 Add Future publisher with tests (#107) 2019-11-26 02:15:43 +03:00
Sergej Jaskiewicz 8cf71e0122 If Multicast receives subscription twice, cancel the second one (#110) 2019-11-25 16:52:55 +03:00
Max Desiatov 130125cb66 Use demand.assertNonZero() in PassthroughSubject (#108) 2019-11-24 21:44:14 +03:00
Daniel Peter 7b3ceae666 CocoaPods support (#103) 2019-11-24 21:17:13 +03:00
Max Desiatov 56202b1663 Fix typo in FilterProducer.swift
> subsription
2019-11-22 20:12:34 +03:00
Sergej Jaskiewicz d0a02de7c5 Warn about unguarded availability in C++ code. Fix the warnings. 2019-11-21 20:18:28 +03:00
Sergej Jaskiewicz c8058edc5f Test that Sink calls the callbacks even without upstream subscription 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 8b685f78a3 Test that ReplaceError saves demand until subscription arrives 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 0816abe33c Remove superfluous 'disable' swiftlint command 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz b6c7560f4c Use pthread_mutex_t instead of std::mutex on non-Darwin platforms
std::mutex doesn't guarantee error checks.
With pthread, we can enable them manually.

Error checks are essential, since the client code may bay be crafted
in such a way that causes a non-recursive lock to be recursively
acuired, which is undefined behavior with std::mutex.
2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 7a9e8b22d2 Bump iOS version for compatibility tests in CircleCI config 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 26f9acd75a Reimplement FlatMap to make all the tests pass 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 4c42b434ca Add more tests for FlatMap 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 8afe945325 Test that CombineIdentifier uses UInt64 under the hood 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz eb879213ef Automatically confirm apt install 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz e402fb3980 Remove redundant Self 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 4f96378c02 Remove OperatorSubscription class 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 243f3d200e Implement PrefixWhile, TryPrefixWhile using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz f1fb5552b5 Implement RemoveDuplicates, TryRemoveDuplicates using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz bfd875ccba Simplify CompactMap and TryCompactMap using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 27da28f378 Simplify Filter and TryFilter using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz cba3a69e74 Implement FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz d634a76c39 A more robust test for Subscribers.Assign 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz b5e31a43ef Fix ReplaceError subscription behavior 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 1bb3583a36 More tests for contract violations in operators
- test the behavior when a value arrives earlier than the subscription
- test the behavior when a completion arrives earlier than
  the subscription
- test the behavior when requesting before a subscription arrives
- test the behavior when cancelling before a subscription arrives
2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 449b8eef48 Test that Record doesn't finilize its Recording 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz d4bdd83a00 Implement the Record publisher 2019-11-12 17:31:29 +03:00
Sergej Jaskiewicz f1cc94adff Implement Publishers.Output 2019-11-11 20:26:31 +03:00
Sergej Jaskiewicz 5e1e10a780 Add CircleCI badge 2019-11-10 15:00:50 +03:00
Sergej Jaskiewicz 5b7358111c Remove redundant 2019-11-10 15:00:50 +03:00
Sergej Jaskiewicz 5f90c4c85f Add Gemfile.lock 2019-11-10 15:00:50 +03:00
Sergej Jaskiewicz 0dec30fcad Switch from Travis CI to CircleCI.
CircleCI has significantly faster builds. They also update their build
machines sooner than Travis CI.
2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz a5ec9723e2 Make the tests pass on 32-bit platforms 2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz 25ac4dfa5f Fix SwiftLint crash
https://github.com/realm/SwiftLint/issues/2793
2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz f5645f605b Remove old benchmarking infrastructure 2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz ffef3ac76c Update CI configuration to run compatibility tests on Xcode 11.2 2019-10-31 14:01:44 +03:00
Sergej Jaskiewicz 36a9f1999c They fix bugs in Combine, we update our awesome test suite!
The bug fixes happened in macOS 10.15.1
2019-10-31 14:01:44 +03:00
Sergej Jaskiewicz f069f9b9fa Audit DropWhile/TryDropWhile for thread safety (#87)
Add more tests, make them pass
2019-10-25 15:45:40 +03:00
Sergej Jaskiewicz 55bdbba0f9 Publishers.Print: cancel new subscription if Inner is already subscribed (#92) 2019-10-24 15:11:55 +03:00
Sergej Jaskiewicz 1290545c49 Audit IgnoreOutput for thread safety, add more tests, make them pass (#88) 2019-10-23 23:44:59 +03:00
Sergej Jaskiewicz aed074af43 Add tests for Subscriptions.empty 2019-10-23 20:30:46 +03:00
Sergej Jaskiewicz 11ec7c89e6 Gybify Encode/Decode, add more tests, make them pass 2019-10-23 20:30:46 +03:00
248 changed files with 38694 additions and 5502 deletions
+326
View File
@@ -0,0 +1,326 @@
macOS_tests_steps: &macOS_tests_steps
steps:
- checkout
- run:
name: Building and running tests in debug mode with coverage
command: |
make test-debug \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--enable-code-coverage --build-path .build-test-debug"
xcrun llvm-cov show \
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests \
> coverage.txt
- run:
name: Building and running tests in debug mode with TSan
command: |
make test-debug-sanitize-thread \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
- run:
name: Building and running tests in release mode
command: |
make test-release \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--build-path .build-test-release"
- run:
name: Generating Xcode project
command: make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
- run:
name: Building for testing on macOS with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-sdk macosx \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing on macOS with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-sdk macosx \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash) -D DerivedData
ubuntu_tests_steps: &ubuntu_tests_steps
steps:
- checkout
- run:
name: Installing dependencies
command: |
apt update -y
apt upgrade -y
apt install -y curl python3.8
- run:
name: "Generating LinuxMain.swift"
command: python3.8 utils/discover_tests.py
- run:
name: Building and running tests in debug mode with coverage
command: |
make test-debug \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--enable-code-coverage \
--disable-index-store \
--build-path .build-test-debug"
llvm-cov show \
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
.build-test-debug/debug/OpenCombinePackageTests.xctest \
> coverage.txt
- run:
name: Building and running tests in debug mode with TSan
command: |
make test-debug-sanitize-thread \
SWIFT_TEST_FLAGS="--disable-index-store \
--build-path .build-test-debug-sanitize-thread" \
- run:
name: Building and running tests in release mode
command: |
make test-release \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--build-path .build-test-release"
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash)
version: 2
jobs:
"Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)":
macos:
xcode: "11.3.0"
environment:
SWIFT_VERSION: "5.1.3"
<<: *macOS_tests_steps
"Execute tests on macOS 10.15.0 (Xcode 12.1.0, Swift 5.3.0)":
macos:
xcode: "12.1.0"
environment:
SWIFT_VERSION: "5.3.0"
<<: *macOS_tests_steps
"Execute tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)":
macos:
xcode: "12.5.0"
environment:
SWIFT_VERSION: "5.4.0"
<<: *macOS_tests_steps
"Execute compatibility tests on iOS 14.5 (Xcode 12.5.0, Swift 5.4.0)":
macos:
xcode: "12.5.0"
environment:
SWIFT_VERSION: "5.4.0"
steps:
- checkout
- run:
name: Generating Xcode project
command: make generate-compatibility-xcodeproj
- run:
name: Building for testing on iOS 14.5 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 12,OS=14.5" \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing against Combine on iOS 14.5 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 12,OS=14.5" \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
"Execute compatibility tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)":
macos:
xcode: "12.5.0"
environment:
SWIFT_VERSION: "5.4.0"
steps:
- checkout
- run:
name: Testing against Combine on macOS 11.4.0
command: |
make test-compatibility
"Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)":
macos:
xcode: "10.2.1"
environment:
BUNDLE_PATH: .bundle # path to install gems and use for caching
SWIFT_VERSION: "5.0.1"
steps:
- checkout
- run:
name: Installing gem dependencies
command: bundle install && bundle clean
- restore_cache:
keys:
- v1-simulator-cache-{{ arch }}
- run:
# CircleCI doesn't have an iOS 9 simulator, so we need to install it manually.
name: Installing iOS 9 simulator
command: |
bundle exec xcversion simulators --install="iOS 9.3"
bundle exec xcversion simulators
xcrun simctl list
- save_cache:
key: v1-simulator-cache-{{ arch }}
paths:
- ~/Library/Caches/XcodeInstall
- run:
name: Generating Xcode project
command: |
make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
xcodebuild -scheme OpenCombine-Package -showdestinations
- run:
name: Building for testing on iOS 9.3 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing on iOS 9.3 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash) -D DerivedData
"Execute tests on Ubuntu 18.04 (Swift 5.0)":
docker:
- image: swift:5.0-bionic
environment:
SWIFT_VERSION: "5.0"
<<: *ubuntu_tests_steps
"Execute tests on Ubuntu 18.04 (Swift 5.1)":
docker:
- image: swift:5.1-bionic
environment:
SWIFT_VERSION: "5.1"
<<: *ubuntu_tests_steps
"Execute tests on Ubuntu 18.04 (Swift 5.2)":
docker:
- image: swift:5.2-bionic
environment:
SWIFT_VERSION: "5.2"
<<: *ubuntu_tests_steps
"Execute tests on Ubuntu 18.04 (Swift 5.3)":
docker:
- image: swift:5.3-bionic
environment:
SWIFT_VERSION: "5.3"
<<: *ubuntu_tests_steps
"Execute tests on Ubuntu 18.04 (Swift 5.4)":
docker:
- image: swift:5.4-bionic
environment:
SWIFT_VERSION: "5.4"
<<: *ubuntu_tests_steps
"Run SwiftLint and Danger":
macos:
xcode: "11.4.0"
environment:
HOMEBREW_NO_AUTO_UPDATE: "1"
steps:
- checkout
- run:
name: Install SwiftLint
command: |
brew install swiftlint
- run:
name: Install danger-swift
command: |
brew install danger/tap/danger-swift
- run:
name: Run danger-swift
command: danger-swift ci
"Run Pod spec lint":
macos:
xcode: "11.4.0"
environment:
HOMEBREW_NO_AUTO_UPDATE: "1"
steps:
- checkout
- run:
name: Pod lib lint
command: |
pod lib lint --allow-warnings --verbose
workflows:
version: 2
"OpenCombine: execute tests on macOS":
jobs:
- "Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)"
- "Execute tests on macOS 10.15.0 (Xcode 12.1.0, Swift 5.3.0)"
- "Execute tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)"
"OpenCombine: execute compatibility tests":
jobs:
# - "Execute compatibility tests on iOS 14.5 (Xcode 12.5.0, Swift 5.4.0)"
- "Execute compatibility tests on macOS 11.4.0 (Xcode 12.5.0, Swift 5.4.0)"
"OpenCombine: execute tests on iOS":
jobs:
- "Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)"
"OpenCombine: execute tests on Linux":
jobs:
- "Execute tests on Ubuntu 18.04 (Swift 5.0)"
- "Execute tests on Ubuntu 18.04 (Swift 5.1)"
- "Execute tests on Ubuntu 18.04 (Swift 5.2)"
- "Execute tests on Ubuntu 18.04 (Swift 5.3)"
- "Execute tests on Ubuntu 18.04 (Swift 5.4)"
"OpenCombine: run SwiftLint and Danger":
jobs:
- "Run SwiftLint and Danger"
"OpenCombine: validate podspec files":
jobs:
- "Run Pod spec lint"
+16
View File
@@ -0,0 +1,16 @@
name: SwiftWasm
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
carton_wasmer_test:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: swiftwasm/swiftwasm-action@v5.3
+4
View File
@@ -3,6 +3,7 @@
/Packages
/*.xcodeproj
/.swiftpm
Tests/LinuxMain.swift
# Created by https://www.gitignore.io/api/Xcode
# Edit at https://www.gitignore.io/?templates=Xcode
@@ -153,3 +154,6 @@ dmypy.json
.pyre/
# End of https://www.gitignore.io/api/Python
.bundle/
node_modules/
+9 -1
View File
@@ -2,6 +2,8 @@ included:
- Sources
- Tests
child_config: Tests/.swiftlint.yml
disabled_rules:
- block_based_kvo
- class_delegate_protocol
@@ -16,11 +18,14 @@ disabled_rules:
- identifier_name
- nesting
- notification_center_detachment
- no_fallthrough_only
- no_space_in_method_call
- redundant_string_enum_value
- todo
- trailing_comma
- type_body_length
- opening_brace
- untyped_error_in_catch
opt_in_rules:
- array_init
@@ -59,11 +64,14 @@ opt_in_rules:
- unneeded_parentheses_in_closure_argument
- untyped_error_in_catch
- unused_import
- unused_private_declaration
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- yoda_condition
implicit_return:
included:
- closure
line_length:
warning: 90
error: 120
-90
View File
@@ -1,90 +0,0 @@
language: generic
addons:
homebrew:
taps:
- danger/tap
packages:
- swiftlint
- danger-swift
update: true
cache:
directories:
- .build
- ~/.danger-swift
- ~/.swiftenv
- ~/Library/Caches/Homebrew
matrix:
include:
- name: "Ubuntu 16.04 | Swift 5.1.1 | Tests"
os: linux
dist: xenial
sudo: required
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" 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" RUN_DANGER="YES" SWIFT_LINT="YES"
before_cache:
- brew cleanup
before_install:
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
cat /proc/cpuinfo;
fi
install:
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
fi
- if [[ $CODE_COVERAGE == "YES" ]]; then
gem install xcpretty;
fi
script:
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-debug;
else
make test-debug;
fi
fi
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-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
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
make generate-xcodeproj;
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
bash <(curl -s https://codecov.io/bash);
fi
+299
View File
@@ -0,0 +1,299 @@
# 0.12.0 (29 Jan 2021)
This release adds a new `OpenCombineShim` product that will conditionally re-export either
Combine on Apple platforms, or OpenCombine on other platforms. Additionally, `ObservableObject`
protocol is now available and working on all platforms.
A bug with `Timer(timeInterval:repeats:block:)` firing immediately not accounting for the passed
`timeInterval` is fixed.
**Merged pull requests:**
- Fix `Timer(timeInterval:repeats:block:)` not accounting `timeInterval` ([#196](https://github.com/OpenCombine/OpenCombine/pull/196)) via [@grigorye](https://github.com/grigorye)
- Add `OpenCombineShim` product for easier importing ([#197](https://github.com/OpenCombine/OpenCombine/pull/197)) via [@MaxDesiatov](https://github.com/MaxDesiatov)
- Implementation for `ObservableObject` with `Mirror` ([#201](https://github.com/OpenCombine/OpenCombine/pull/201)) via [@kateinoigakukun](https://github.com/kateinoigakukun)
# 0.11.0 (29 Oct 2020)
This release is compatible with Xcode 12.1.
### Additions
- `Publisher.assigned(to:)` method that accepts a `Published.Publisher`.
- New `Publisher.switchToLatest()` overloads.
- New `Publisher.flatMap(maxPublishers:_:)` overloads.
- `Optional.publisher` property.
- New `_Introspection` protocol that allows to track and explore the subscription graph and data flow.
### Bugfixes
- The project should now compile without warnings.
- The following entities have been updated to match the behavior of the newest Combine version:
- `Subscribers.Assign`
- `Publishers.Breakpoint`
- `Publishers.Buffer`
- `CombineIdentifier`
- `Publishers.CompactMap`
- `Publishers.Concatenate`
- `Publishers.Debounce`
- `Publishers.Delay`
- `DispatchQueue.SchedulerTimeType.Stride`
- `Publishers.Drop`
- `Publishers.Encode`
- `Publishers.Decode`
- `Publishers.Filter`
- `Publishers.HandleEvents`
- `Publishers.IgnoreOutput`
- `Publishers.MeasureInterval`
- `OperationQueue` scheduler
- `Published`
- `Publishers.ReceiveOn`
- `Publishers.ReplaceError`
- `RunLoop scheduler`
- `Publishers.Sequence`
- `Subscribers.Sink`
- `Publishers.SubscribeOn`
- `Publishers.Timeout`
- `Timer` publisher
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
# 0.10.2 (23 Oct 2020)
### Bugfixes
- Fixed a crash caused by recursive acquisition of a non-recursive lock in SubbjectSubscriber (#186, thanks @stuaustin for the bug report)
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
# 0.10.1 (4 Oct 2020)
### Bugfixes
- Fixed build errors on Linux with Swift 5.0 and Swift 5.3 toolchains (thanks, @adamleonard and @devmaximilian)
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
# 0.10.0 (28 Jun 2020)
This release is compatible with Xcode 11.5.
### Additions
- `Timer.publish(every:tolerance:on:in:options:)` (#156, thank you @MaxDesiatov)
- `OperationQueue` scheduler (#165)
- `Publishers.Timeout` (#164)
- `Publishers.Debounce` (#133)
### Bugfixes
- `PassthroughSubject`, `CurrentValueSubject` and `Future` have been rewritten from scratch. They are now faster, more correct and no longer leak subscriptions (#170).
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
# 0.9.0 (12 Jun 2020)
This release is compatible with Xcode 11.5.
### Additions
- The `Subscribers.Demand` struct can be nicely formatted in LLDB (#146, thank you @mayoff).
- `Publishers.SwitchToLatest` (#142).
- The `RunLoop` scheduler in `OpenCombineFoundation` (#131).
- `Publishers.Catch` and `Publishers.TryCatch` (#140).
### Bugfixes
- Worked around a [bug in the Swift compiler](https://bugs.swift.org/browse/SR-11680) when building the `COpenCombineHelpers` target (#145, thank you @mayoff).
- Improved documentation.
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1 and later.
# 0.8.0 (17 Jan 2020)
This release is compatible with Xcode 11.3.1.
### Additions
- `Publishers.ReplaceEmpty` (#122, thank you @spadafiva)
- `NotificationCenter.Publisher` (#84)
- `URLSession.DataTaskPublisher` (#127)
- `Publishers.DropUntilOutput` (#136)
- `Publishers.CollectByCount` (#137)
- `Publishers.AssertNoFailure` (#138)
- `Publishers.Buffer` (#143)
### Bugfixes
- Fixed integer overflows in `DispatchQueue.SchedulerTimeType.Stride` (#126, #130)
- Fixed the 'default will never be executed' warning on non-Darwin platforms (like Linux) (#129)
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
# 0.7.0 (10 Dec 2019)
This release is compatible with Xcode 11.2.1.
### Additions
- `Publishers.Delay` (#114)
- `Publishers.ReceiveOn` (#115)
- `Publishers.SubscribeOn` (#116)
- `Publishers.MeasureInterval` (#117)
- `Publishers.Breakpoint` (#118)
- `Publishers.HandleEvents` (#118)
- `Publishers.Concatenate` (#90)
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
# 0.6.0 (26 Nov 2019)
This release is compatible with Xcode 11.2.1.
### Thread safety
- `Publishers.IgnoreOutput` has been audited for thread safety (#88)
- `Publishers.DropWhile` and `Publishers.TryDropWhile` have been audited for thread safety (#87)
### Additions
- `Publishers.Output` (#91)
- `Record` (#100)
- `Publishers.RemoveDuplicates`, `Publishers.TryRemoveDuplicates` (#89)
- `Publishers.PrefixWhile`, `Publishers.TryPrefixWhile` (#89)
- `Future` (#107, thanks @MaxDesiatov!)
### Bugfixes
- The behavior of the `Publishers.Encode` and `Publishers.Decode` subscriptions is fixed (#112)
- The behavior of the `Publishers.IgnoreOutput` subscription is fixed (#88)
- The behavior of the `Publishers.Print` subscription is fixed (#92)
- The behavior of the `Publishers.ReplaceError` subscription is fixed (#89)
- The behavior of the `Publishers.Filter` and `Publishers.TryFilter` subscriptions is fixed (#89)
- The behavior of the `Publishers.CompactMap` and `Publishers.TryCompactMap` subscriptions is fixed (#89)
- The behavior of the `Publishers.Multicast` subscription is fixed (#110)
- `Publishers.FlatMap` is reimplemented from scratch. Its behavior is fixed in many ways, it now fully matches that of Combine (#89)
- `@Published` property wrapper is fixed! (#112)
- The behavior of `DispatchQueue.SchedulerTimeType` is fixed to match that of the latest SDKs (#96)
- OpenCombine is now usable on 32 bit platforms. Why? Because we can.
### Known issues
- The default implementation of the `objectWillChange` requirement of the `ObservableObject` protocol is not available in Swift 5.1.
# 0.5.0 (17 Oct 2019)
This release is compatible with Xcode 11.1.
### Additions
- `Publishers.MapKeyPath` (#71)
- `Publishers.Reduce` (#76)
- `Publishers.TryReduce` (#76)
- `Publishers.Last` (#76)
- `Publishers.LastWhere` (#76)
- `Publishers.TryLastWhere` (#76)
- `Publishers.AllSatisfy` (#76)
- `Publishers.TryAllSatisfy` (#76)
- `Publishers.Contains` (#76)
- `Publishers.ContainsWhere` (#76)
- `Publishers.TryContainsWhere` (#76)
- `Publishers.Collect` (#76)
- `Publishers.Comparison` (#76)
- `Publishers.Drop` (#70, thank you @5sw!)
- `Publishers.Scan` (#83, thank you @epatey!)
- `Publishers.TryScan` (#83, thank you @epatey!)
### Bugfixes
- `Publishers.Print` doesn't print a redundant whitespace anymore.
### Known issues
- `@Published` property wrapper doesn't work yet
# 0.4.0 (8 Oct 2019)
This release is compatible with Xcode 11.1.
### Thread safety
- `SubjectSubscriber` (which is used when you subscribe a subject to a publisher) has been audited for thread-safety
- `Publishers.Multicast` has been audited for thread safety (#63)
- `Publishers.TryMap` has been audited for thread safety
- `Just` has been audited for thread safety
- `Optional.Publisher` has been audited for thread safety
- `Publishers.Sequence` has been audited for thread safety
- `Publishers.ReplaceError` has been audited for thread safety
- `Subscribers.Assign` has been audited for thread safety
- `Subscribers.Sink` has been audited for thread safety
### Bugfixes
- The semantics of `Publishers.Print`, `Publishers.TryMap` have been fixed
- Fix `iterator.next()` being called twice in `Publishers.Sequence` (#62)
- The default initializer of `CombineIdentifier` (the one that takes no arguments) is now much faster (#66, #69)
- When `Publishers.Sequence` subscription is cancelled while it emits values, the cancellation is respected (#73, thanks @5sw!)
### Additions
- `DispatchQueueScheduler` (#46)
- `Equatable` conformances for `First`, `ReplaceError`
- Added `eraseToAnyPublisher()` method (#59, thanks @evyasafhouzz for reporting!)
- `Publishers.MakeConnectable` (#61)
- `Publishers.Autoconnect` (#60)
- `Publishers.Share` (#60)
### Known issues
- `@Published` property wrapper doesn't work yet
# 0.3.0 (13 Sep 2019)
Among other things this release is compatible with Xcode 11.0 GM seed.
### Bugfixes
- Store newly send value in internal variable inside CurrentValueObject (#39, thanks @FranzBusch!)
### Additions
- `Filter`/`TryFilter` (#22, thanks @spadafiva!)
- `First`/`FirstWhere`/`TryFirstWhere` (#22, thanks again @spadafiva!)
- `CompactMap`/`TryCompacrMap` (#32)
- `IgnoreOutput` (#44, thanks @epatey!)
- `ReplaceError` (#50, thanks @vladiulianbogdan!)
- `FlatMap` (#45, thanks again @epatey!)
### Known issues
- `@Published` property wrapper doesn't work yet
# 0.2.0 (31 Jul 2019)
Updated for the newest Xcode 11.0 beta 5
# 0.1.0 (4 Jul 2019)
The first pre-pre-pre-alpha release is here!
Lots of stuff still unimplemented.
For now we have:
- `Just`
- `Publishers.Decode`
- `Publishers.DropWhile`
- `Publishers.Empty`
- `Publishers.Encode`
- `Publishers.Fail`
- `Publishers.Map`
- `Publishers.Multicast`
- `Publishers.Once`
- `Publishers.Optional`
- `Publishers.Print`
- `Publishers.Sequence`
- `Subscribers.Assign`
- `Subscribers.Completion`
- `Subscribers.Demand`
- `Subscribers.Sink`
- `AnyCancellable`
- `AnyPublisher`
- `AnySubject`
- `AnySubscriber`
- `Cancellable`
- `CombineIdentifier`
- `ConnectablePublisher`
- `CurrentValueSubject`
- `CustomCombineIdentifierConvertible`
- `ImmediateScheduler`
- `PassthroughSubject`
- `Publisher`
- `Result`
- `Scheduler`
- `Subject`
- `Subscriber`
- `Subscription`
+3 -3
View File
@@ -66,10 +66,10 @@ do {
}
}
SwiftLint.lint(inline: true,
SwiftLint.lint(.all(directory: nil),
inline: true,
configFile: ".swiftlint.yml",
strict: true,
lintAllFiles: true)
strict: true)
if danger.warnings.isEmpty, danger.fails.isEmpty {
markdown("LGTM")
+3
View File
@@ -0,0 +1,3 @@
source 'https://rubygems.org'
gem 'xcode-install'
+162
View File
@@ -0,0 +1,162 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.1)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
excon (0.71.0)
faraday (0.17.0)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.134.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.23.9)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.3.2)
google-cloud-env (~> 1.0)
google-cloud-env (1.2.1)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
google-api-client (~> 0.23)
google-cloud-core (~> 1.2)
googleauth (>= 0.6.2, < 0.10.0)
googleauth (0.6.7)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
json (2.3.1)
jwt (2.1.0)
memoist (0.16.1)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mini_magick (4.9.5)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.3.0)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcode-install (2.6.2)
claide (>= 0.9.1, < 1.1.0)
fastlane (>= 2.1.0, < 3.0.0)
xcodeproj (1.13.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
xcode-install
BUNDLED WITH
2.0.1
+11 -9
View File
@@ -1,32 +1,34 @@
SWIFT_EXE=swift
SWIFT_TEST_FLAGS=
SWIFT_BUILD_FLAGS=-Xcc -Wunguarded-availability
debug:
swift build -c debug
$(SWIFT_EXE) build -c debug $(SWIFT_BUILD_FLAGS)
release:
swift build -c release
$(SWIFT_EXE) build -c release $(SWIFT_BUILD_FLAGS)
test-debug:
swift test -c debug $(SWIFT_TEST_FLAGS)
$(SWIFT_EXE) test -c debug $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
test-debug-sanitize-thread:
swift test -c debug --sanitize thread $(SWIFT_TEST_FLAGS)
$(SWIFT_EXE) test -c debug --sanitize thread $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
test-release:
swift test -c release $(SWIFT_TEST_FLAGS)
$(SWIFT_EXE) test -c release $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
swift-version:
swift -version
$(SWIFT_EXE) -version
test-compatibility:
swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
$(SWIFT_EXE) test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
generate-compatibility-xcodeproj:
swift package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
$(SWIFT_EXE) package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
open OpenCombine.xcodeproj
generate-xcodeproj:
swift package generate-xcodeproj --enable-code-coverage
$(SWIFT_EXE) package $(SWIFT_BUILD_FLAGS) generate-xcodeproj --enable-code-coverage
gyb:
$(shell ./utils/recursively_gyb.sh)
+27
View File
@@ -0,0 +1,27 @@
Pod::Spec.new do |spec|
spec.name = "OpenCombine"
spec.version = "0.12.0"
spec.summary = "Open source implementation of Apple's Combine framework for processing values over time."
spec.description = <<-DESC
An open source implementation of Apple's Combine framework for processing values over time.
DESC
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
spec.license = "MIT"
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
spec.swift_version = "5.0"
spec.osx.deployment_target = "10.10"
spec.ios.deployment_target = "8.0"
spec.watchos.deployment_target = "2.0"
spec.tvos.deployment_target = "9.0"
spec.source_files = "Sources/COpenCombineHelpers/**/*.{h,cpp}", "Sources/OpenCombine/**/*.swift"
spec.public_header_files = "Sources/COpenCombineHelpers/include/*.h"
spec.libraries = "c++"
end
+25
View File
@@ -0,0 +1,25 @@
Pod::Spec.new do |spec|
spec.name = "OpenCombineDispatch"
spec.version = "0.12.0"
spec.summary = "OpenCombine + Dispatch interoperability"
spec.description = <<-DESC
Extends `DispatchQueue` with conformance to the `Scheduler` protocol
DESC
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
spec.license = "MIT"
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
spec.swift_version = "5.0"
spec.osx.deployment_target = "10.10"
spec.ios.deployment_target = "8.0"
spec.watchos.deployment_target = "2.0"
spec.tvos.deployment_target = "9.0"
spec.source_files = "Sources/OpenCombineDispatch/**/*.swift"
spec.dependency "OpenCombine", '>= 0.10.2'
end
+25
View File
@@ -0,0 +1,25 @@
Pod::Spec.new do |spec|
spec.name = "OpenCombineFoundation"
spec.version = "0.12.0"
spec.summary = "OpenCombine + OpenCombineFoundation interoperability"
spec.description = <<-DESC
Adds publishers to Foundation types like NotificationCenter, URLSession etc.
DESC
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
spec.license = "MIT"
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
spec.swift_version = "5.0"
spec.osx.deployment_target = "10.10"
spec.ios.deployment_target = "8.0"
spec.watchos.deployment_target = "2.0"
spec.tvos.deployment_target = "9.0"
spec.source_files = "Sources/OpenCombineFoundation/**/*.swift"
spec.dependency "OpenCombine", '>= 0.10.2'
end
-25
View File
@@ -1,25 +0,0 @@
{
"object": {
"pins": [
{
"package": "GottaGoFast",
"repositoryURL": "https://github.com/broadwaylamb/GottaGoFast.git",
"state": {
"branch": null,
"revision": "85e1f5104fb1ede87d6f31acc0445555007c474c",
"version": "0.1.0"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f",
"version": "2.0.0"
}
}
]
},
"version": 1
}
+74 -10
View File
@@ -1,25 +1,89 @@
// swift-tools-version:5.0
// swift-tools-version:5.3
import PackageDescription
// This list should be updated whenever SwiftPM adds support for a new platform.
// See: https://bugs.swift.org/browse/SR-13814
let supportedPlatforms: [Platform] = [
.macOS,
.iOS,
.watchOS,
.tvOS,
.linux,
.android,
// Disable Windows because of https://bugs.swift.org/browse/SR-13817
// .windows,
.wasi,
]
let package = Package(
name: "OpenCombine",
products: [
.library(name: "OpenCombine", targets: ["OpenCombine"]),
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
],
dependencies: [
.package(url: "https://github.com/broadwaylamb/GottaGoFast.git", from: "0.1.0")
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
],
targets: [
.target(name: "COpenCombineHelpers"),
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
.target(
name: "OpenCombine",
dependencies: [
.target(name: "COpenCombineHelpers",
condition: .when(platforms: supportedPlatforms.except([.wasi])))
],
exclude: [
"Publishers/Publishers.Encode.swift.gyb",
"Publishers/Publishers.MapKeyPath.swift.gyb",
"Publishers/Publishers.Catch.swift.gyb"
],
swiftSettings: [.define("WASI", .when(platforms: [.wasi]))]
),
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine",
"OpenCombineDispatch",
"GottaGoFast"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
.target(
name: "OpenCombineFoundation",
dependencies: [
"OpenCombine",
.target(name: "COpenCombineHelpers",
condition: .when(platforms: supportedPlatforms.except([.wasi])))
]
),
.target(
name: "OpenCombineShim",
dependencies: [
"OpenCombine",
.target(name: "OpenCombineDispatch",
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
.target(name: "OpenCombineFoundation",
condition: .when(platforms: supportedPlatforms.except([.wasi])))
]
),
.testTarget(
name: "OpenCombineTests",
dependencies: [
"OpenCombine",
.target(name: "OpenCombineDispatch",
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
.target(name: "OpenCombineFoundation",
condition: .when(platforms: supportedPlatforms.except([.wasi]))),
],
swiftSettings: [
.unsafeFlags(["-enable-testing"]),
.define("WASI", .when(platforms: [.wasi]))
]
)
],
cxxLanguageStandard: .cxx1z
)
// MARK: Helpers
extension Array where Element == Platform {
func except(_ exceptions: [Platform]) -> [Platform] {
// See: https://bugs.swift.org/browse/SR-13813
let exceptionsDescriptions = exceptions.map(String.init(describing:))
return filter { platform in
!exceptionsDescriptions.contains(String(describing: platform))
}
}
}
+34
View File
@@ -0,0 +1,34 @@
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "OpenCombine",
products: [
.library(name: "OpenCombine", targets: ["OpenCombine"]),
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
],
targets: [
.target(name: "COpenCombineHelpers"),
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
"COpenCombineHelpers"]),
.target(
name: "OpenCombineShim",
dependencies: [
"OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation",
]
),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
],
cxxLanguageStandard: .cxx1z
)
+34
View File
@@ -0,0 +1,34 @@
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "OpenCombine",
products: [
.library(name: "OpenCombine", targets: ["OpenCombine"]),
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
],
targets: [
.target(name: "COpenCombineHelpers"),
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
"COpenCombineHelpers"]),
.target(
name: "OpenCombineShim",
dependencies: [
"OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation",
]
),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
],
cxxLanguageStandard: .cxx1z
)
+34
View File
@@ -0,0 +1,34 @@
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "OpenCombine",
products: [
.library(name: "OpenCombine", targets: ["OpenCombine"]),
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
.library(name: "OpenCombineShim", targets: ["OpenCombineShim"]),
],
targets: [
.target(name: "COpenCombineHelpers"),
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
"COpenCombineHelpers"]),
.target(
name: "OpenCombineShim",
dependencies: [
"OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation",
]
),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
],
cxxLanguageStandard: .cxx1z
)
+91 -8
View File
@@ -1,23 +1,71 @@
# OpenCombine
[![Build Status](https://travis-ci.org/broadwaylamb/OpenCombine.svg?branch=master)](https://travis-ci.org/broadwaylamb/OpenCombine)
[![codecov](https://codecov.io/gh/broadwaylamb/OpenCombine/branch/master/graph/badge.svg)](https://codecov.io/gh/broadwaylamb/OpenCombine)
[![OpenCombine](https://circleci.com/gh/OpenCombine/OpenCombine.svg?style=svg)](https://circleci.com/gh/OpenCombine/OpenCombine)
[![codecov](https://codecov.io/gh/OpenCombine/OpenCombine/branch/master/graph/badge.svg)](https://codecov.io/gh/OpenCombine/OpenCombine)
![Language](https://img.shields.io/badge/Swift-5.0-orange.svg)
![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg)
![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Wasm-lightgrey.svg)
![Cocoapods](https://img.shields.io/cocoapods/v/OpenCombine?color=blue)
[<img src="https://img.shields.io/badge/slack-OpenCombine-yellow.svg?logo=slack">](https://join.slack.com/t/opencombine/shared_invite/enQtNzE2MjE5NzkxODI0LTYxMjkzNDUxZWViZWI1Njc2YjBhODgxNjRjOTdkZTcxOGU2ZjJjZjYxMGI3NWZkN2RkNGFmZTUzNmU3MGE2ZWM)
Open-source implementation of Apple's [Combine](https://developer.apple.com/documentation/combine) framework for processing values over time.
The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux and Windows.
The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux and WebAssembly.
The project is in early development.
### Installation
`OpenCombine` contains three public targets: `OpenCombine`, `OpenCombineFoundation` and `OpenCombineDispatch` (the fourth one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`. The same applies to Foundation: if you want to use, for instance, `NotificationCenter` or `URLSession` publishers, you'll need to also import `OpenCombineFoundation`.
If you develop code for multiple platforms, you may find it more convenient to import the
`OpenCombineShim` module instead. It conditionally re-exports Combine on Apple platforms (if
available), and all OpenCombine modules on other platforms. You can import `OpenCombineShim` only
when using SwiftPM. It is not currently available for CocoaPods.
##### Swift Package Manager
###### Swift Package
To add `OpenCombine` to your [SwiftPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file. `OpenCombineDispatch` and `OpenCombineFoundation` products are currently not supported on WebAssembly. If your project targets WebAssembly exclusively, you should omit them from the list of your dependencies. If it targets multiple platforms including WebAssembly, depend on them only on non-WebAssembly platforms with [conditional target dependencies](https://github.com/apple/swift-evolution/blob/main/proposals/0273-swiftpm-conditional-target-dependencies.md).
```swift
dependencies: [
.package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.12.0")
],
targets: [
.target(
name: "MyAwesomePackage",
dependencies: [
"OpenCombine",
.product(name: "OpenCombineFoundation", package: "OpenCombine"),
.product(name: "OpenCombineDispatch", package: "OpenCombine")
]
),
]
```
###### Xcode
`OpenCombine` can also be added as a SwiftPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
To do so, open Xcode, use **File****Swift Packages****Add Package Dependency…**, enter the [repository URL](https://github.com/OpenCombine/OpenCombine.git), choose the latest available version, and activate the checkboxes:
<p align="center">
<img alt="Select the OpenCombine and OpenCombineDispatch targets"
src="https://user-images.githubusercontent.com/16309982/67618468-bd379f80-f7f8-11e9-917f-e76e878a1aee.png" width="70%">
</p>
##### CocoaPods
To add `OpenCombine` to a project using [CocoaPods](https://cocoapods.org/), add `OpenCombine` and `OpenCombineDispatch` to the list of target dependencies in your `Podfile`.
```ruby
pod 'OpenCombine', '~> 0.12.0'
pod 'OpenCombineDispatch', '~> 0.12.0'
pod 'OpenCombineFoundation', '~> 0.12.0'
```
### Contributing
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
Please refer to the [issue #1](https://github.com/broadwaylamb/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/broadwaylamb/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
Please refer to the [issue #1](https://github.com/OpenCombine/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/OpenCombine/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff851c4c1dbf0a872e26) to observe Apple's Combine API changes between different Xcode (beta) versions, or to [this gist](https://gist.github.com/broadwaylamb/82dc2ce4ffbe06527c2c352b8f10910f) to see the relevant contents of the .swiftinterface file for Combine.
You can refer to [this repo](https://github.com/OpenCombine/combine-interfaces) to observe Apple's Combine API and documentation changes between different Xcode (beta) versions.
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
@@ -27,7 +75,29 @@ $ 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.
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/OpenCombine/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
#### Releasing a new version
1. Create a new branch from master and call it `release/<major>.<minor>.<patch>`.
1. Replace the usages of the old version in `README.md` with the new version (make sure to check the [Swift Package Manager](#swift-package-manager) and [CocoaPods](#cocoapods) sections).
1. Bump the version in `OpenCombine.podspec`, `OpenCombineDispatch.podspec` and `OpenCombineFoundation.podspec`. In the latter two you will also need to set the `spec.dependency "OpenCombine"` property to the **previous** version. Why? Because otherwise the `pod lib lint` command that we run on our regular CI will fail when validating the `OpenCombineDispatch` and `OpenCombineFoundation` podspecs, since the dependencies are not yet in the trunk. If we set the dependencies to the previous version (which is already in the trunk), everything will be fine. This is purely to make the CI work. The clients will not experience any issues, since the version is specified as `>=`.
1. Create a pull request to master for the release branch and make sure the CI passes.
1. Merge the pull request.
1. In the GitHub web interface on the [releases](https://github.com/OpenCombine/OpenCombine/releases) page, click the **Draft a new release** button.
1. The **Tag version** and **Release title** fields should be filled with the version number.
1. The description of the release should be consistent with the previous releases. It is a good practice to divide the description into several sections: additions, bugfixes, known issues etc. Also, be sure to mention the nicknames of the contributors of the new release.
1. Publish the release.
1. Switch to the master branch and pull the changes.
1. Push the release to CocoaPods trunk. For that, execute the following commands:
```
pod trunk push OpenCombine.podspec --verbose --allow-warnings
pod trunk push OpenCombineDispatch.podspec --verbose --allow-warnings
pod trunk push OpenCombineFoundation.podspec --verbose --allow-warnings
```
Note that you need to be one of the owners of the pod for that.
#### GYB
@@ -49,3 +119,16 @@ GYB template files have the `.gyb` extension. Run `make gyb` to generate Swift c
templates. The generated files are prefixed with `GENERATED-` and are checked into source control. Those
files should never be edited directly. Instead, the `.gyb` template should be edited, and after that the files
should be regenerated using `make gyb`.
#### Debugger Support
The file `opencombine_lldb.py` defines some `lldb` type summaries for easier debugging. These type summaries improve the way `lldb` and Xcode display some OpenCombine values.
To use `opencombine_lldb.py`, figure out its full path. Let's say the full path is `~/projects/OpenCombine/opencombine_lldb.py`. Then the following statement to your `~/.lldbinit` file:
command script import ~/projects/OpenCombine/opencombine_lldb.py
Currently, `opencombine_lldb.py` defines type summaries for these types:
- `Subscribers.Demand`
- That's all for now.
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
// From /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/swift/Foundation.swiftmodule/x86_64.swiftinterface
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.1.1 (swiftlang-1100.8.275.1 clang-1100.0.32.1)
// swift-module-flags: -target x86_64-apple-macosx10.15 -enable-objc-interop -autolink-force-load -enable-library-evolution -module-link-name swiftFoundation -swift-version 5 -O -enforce-exclusivity=unchecked -module-name Foundation
public typealias Published = Combine.Published
public typealias ObservableObject = Combine.ObservableObject
public protocol _KeyValueCodingAndObservingPublishing {
}
extension NSObject : Foundation._KeyValueCodingAndObservingPublishing {
}
extension _KeyValueCodingAndObservingPublishing where Self : ObjectiveC.NSObject {
public func publisher<Value>(for keyPath: Swift.KeyPath<Self, Value>, options: Foundation.NSKeyValueObservingOptions = [.initial, .new]) -> ObjectiveC.NSObject.KeyValueObservingPublisher<Self, Value>
}
extension NSObject.KeyValueObservingPublisher {
public func didChange() -> Combine.Publishers.Map<ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, Swift.Void>
}
extension NSObject {
public struct KeyValueObservingPublisher<Subject, Value> : Swift.Equatable where Subject : ObjectiveC.NSObject {
public let object: Subject
public let keyPath: Swift.KeyPath<Subject, Value>
public let options: Foundation.NSKeyValueObservingOptions
public init(object: Subject, keyPath: Swift.KeyPath<Subject, Value>, options: Foundation.NSKeyValueObservingOptions)
public static func == (lhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, rhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>) -> Swift.Bool
}
}
extension NSObject.KeyValueObservingPublisher : Combine.Publisher {
public typealias Output = Value
public typealias Failure = Swift.Never
public func receive<S>(subscriber: S) where Value == S.Input, S : Combine.Subscriber, S.Failure == ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>.Failure
}
@@ -8,55 +8,150 @@
#include "COpenCombineHelpers.h"
#include <atomic>
#include <mutex>
#include <cstdlib>
#include <system_error>
#include <pthread.h>
#include <signal.h>
#ifdef __APPLE__
#include <os/lock.h>
#endif // __APPLE__
// Throwing exceptions through language boundaries is undefined behavior,
// so we must catch all of them in our extern "C" functions.
#define OPENCOMBINE_HANDLE_EXCEPTION_BEGIN try {
#define OPENCOMBINE_HANDLE_EXCEPTION_END } catch (...) { abort(); }
// std::terminate will print the type and the error message of the in-flight exception.
#define OPENCOMBINE_HANDLE_EXCEPTION_END } catch (...) { std::terminate(); }
// See 'double expansion trick'
#define OPENCOMBINE_STRINGIFY(value) #value
#define OPENCOMBINE_STRINGIFY_(value) OPENCOMBINE_STRINGIFY(value)
#define OPENCOMBINE_STRING_LINE_NUMBER OPENCOMBINE_STRINGIFY_(__LINE__)
// Throw an exception if the argument is non-zero with filename and line where the error
// occured.
#define OPENCOMBINE_HANDLE_PTHREAD_CALL(errc) \
if ((errc) != 0) { \
const char* what = __FILE__ ":" OPENCOMBINE_STRING_LINE_NUMBER ": " #errc; \
throw std::system_error((errc), std::system_category(), what); \
}
namespace {
static std::atomic<uint64_t> next_combine_identifier;
std::atomic<uint64_t> next_combine_identifier;
class PlatformIndependentMutex {
public:
virtual void lock() = 0;
virtual void unlock() = 0;
virtual void assertOwner() {}
virtual ~PlatformIndependentMutex() {}
virtual ~PlatformIndependentMutex() noexcept(false) {}
};
template <typename Mutex>
class GenericMutex final : PlatformIndependentMutex {
Mutex mutex_;
class PThreadMutex : PlatformIndependentMutex {
private:
pthread_mutex_t mutex_;
public:
void lock() override {
mutex_.lock();
PThreadMutex() {
Attributes attrs;
attrs.setErrorCheck();
initialize(attrs);
}
void unlock() override {
mutex_.unlock();
PThreadMutex(const PThreadMutex&) = delete;
PThreadMutex& operator=(const PThreadMutex&) = delete;
PThreadMutex(PThreadMutex&&) = delete;
PThreadMutex& operator=(PThreadMutex&&) = delete;
void lock() override final {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_lock(&mutex_));
}
void unlock() override final {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_unlock(&mutex_));
}
~PThreadMutex() {
// Yep, this destructor may throw. This is deliberate, since pthread_mutex_destroy
// may fail.
//
// The altrenative is to just silently ignore the error, which is even worse.
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_destroy(&mutex_));
}
protected:
class Attributes {
pthread_mutexattr_t attrs_;
public:
Attributes() {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_init(&attrs_));
}
Attributes(const Attributes&) = delete;
Attributes& operator=(const Attributes&) = delete;
Attributes(Attributes&&) = delete;
Attributes& operator=(Attributes&&) = delete;
const pthread_mutexattr_t* raw() const noexcept {
return &attrs_;
}
void setRecursive() {
setType(PTHREAD_MUTEX_RECURSIVE);
}
void setErrorCheck() {
setType(PTHREAD_MUTEX_ERRORCHECK);
}
~Attributes() noexcept(false) {
// Yep, this destructor may throw. This is deliberate,
// since pthread_mutexattr_destroy may fail.
//
// The altrenative is to just silently ignore the error, which is even worse.
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_destroy(&attrs_));
}
private:
void setType(int type) {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_settype(&attrs_, type));
}
};
void initialize(const Attributes& attributes) {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_init(&mutex_, attributes.raw()));
}
};
class PThreadRecursiveMutex final : PThreadMutex {
public:
PThreadRecursiveMutex() {
Attributes attrs;
attrs.setRecursive();
initialize(attrs);
}
PThreadRecursiveMutex(const PThreadRecursiveMutex&) = delete;
PThreadRecursiveMutex& operator=(const PThreadRecursiveMutex&) = delete;
PThreadRecursiveMutex(PThreadRecursiveMutex&&) = delete;
PThreadRecursiveMutex& operator=(PThreadRecursiveMutex&&) = delete;
};
#ifdef __APPLE__
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 {
class OS_UNFAIR_LOCK_AVAILABILITY OSUnfairLock final : PlatformIndependentMutex {
os_unfair_lock mutex_ = OS_UNFAIR_LOCK_INIT;
public:
GenericMutex() = default;
GenericMutex(const GenericMutex&) = delete;
GenericMutex& operator=(const GenericMutex&) = delete;
OSUnfairLock() = default;
OSUnfairLock(const OSUnfairLock&) = delete;
OSUnfairLock& operator=(const OSUnfairLock&) = delete;
OSUnfairLock(OSUnfairLock&&) = delete;
OSUnfairLock& operator=(OSUnfairLock&&) = delete;
void lock() override {
os_unfair_lock_lock(&mutex_);
@@ -65,6 +160,10 @@ public:
void unlock() override {
os_unfair_lock_unlock(&mutex_);
}
void assertOwner() override {
os_unfair_lock_assert_owner(&mutex_);
}
};
#endif // __APPLE__
@@ -80,13 +179,13 @@ OpenCombineUnfairLock opencombine_unfair_lock_alloc(void) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
#ifdef __APPLE__
if (isOSUnfairLockAvailable()) {
return {new GenericMutex<os_unfair_lock>};
if (__builtin_available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) {
return {new OSUnfairLock};
} else {
return {new GenericMutex<std::mutex>};
return {new PThreadMutex};
}
#else
return {new GenericMutex<std::mutex>};
return {new PThreadMutex};
#endif
OPENCOMBINE_HANDLE_EXCEPTION_END
@@ -95,7 +194,7 @@ OpenCombineUnfairLock opencombine_unfair_lock_alloc(void) {
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>};
return {new PThreadRecursiveMutex};
OPENCOMBINE_HANDLE_EXCEPTION_END
}
@@ -111,6 +210,12 @@ void opencombine_unfair_lock_unlock(OpenCombineUnfairLock mutex) {
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(mutex.opaque)->assertOwner();
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock lock) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
@@ -131,4 +236,8 @@ void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lo
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
}
void opencombine_stop_in_debugger(void) {
raise(SIGTRAP);
}
} // extern "C"
@@ -23,47 +23,54 @@ extern "C" {
#pragma mark - CombineIdentifier
uint64_t opencombine_next_combine_identifier(void)
OPENCOMBINE_SWIFT_NAME(nextCombineIdentifier());
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;
} 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());
OPENCOMBINE_SWIFT_NAME(__UnfairLock.allocate());
void opencombine_unfair_lock_lock(OpenCombineUnfairLock)
OPENCOMBINE_SWIFT_NAME(UnfairLock.lock(self:));
OPENCOMBINE_SWIFT_NAME(__UnfairLock.lock(self:));
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock)
OPENCOMBINE_SWIFT_NAME(UnfairLock.unlock(self:));
OPENCOMBINE_SWIFT_NAME(__UnfairLock.unlock(self:));
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.assertOwner(self:));
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock)
OPENCOMBINE_SWIFT_NAME(UnfairLock.deallocate(self:));
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;
} OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock) OpenCombineUnfairRecursiveLock;
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void)
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.allocate());
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.allocate());
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock)
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.lock(self:));
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.lock(self:));
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock)
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.unlock(self:));
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.unlock(self:));
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock)
OPENCOMBINE_SWIFT_NAME(UnfairRecursiveLock.deallocate(self:));
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.deallocate(self:));
#pragma mark - Breakpoint
void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
#ifdef __cplusplus
} // extern "C"
+8 -7
View File
@@ -10,7 +10,8 @@
/// Subscriber implementations can use this type to provide a cancellation token that
/// makes it possible for a caller to cancel a publisher, but not to use the
/// `Subscription` object to request items.
/// An AnyCancellable instance automatically calls `cancel()` when deinitialized.
///
/// An `AnyCancellable` instance automatically calls `cancel()` when deinitialized.
public final class AnyCancellable: Cancellable, Hashable {
private var _cancel: (() -> Void)?
@@ -46,18 +47,18 @@ public final class AnyCancellable: Cancellable, Hashable {
extension AnyCancellable {
/// Stores this AnyCancellable in the specified collection.
/// Parameters:
/// - collection: The collection to store this AnyCancellable.
/// Stores this type-erasing cancellable instance in the specified collection.
///
/// - Parameter collection: The collection in which to store this `AnyCancellable`.
public func store<Cancellables: RangeReplaceableCollection>(
in collection: inout Cancellables
) where Cancellables.Element == AnyCancellable {
collection.append(self)
}
/// Stores this AnyCancellable in the specified set.
/// Parameters:
/// - collection: The set to store this AnyCancellable.
/// Stores this type-erasing cancellable instance in the specified collection.
///
/// - Parameter collection: The collection in which to store this `AnyCancellable`.
public func store(in set: inout Set<AnyCancellable>) {
set.insert(self)
}
+53 -7
View File
@@ -9,8 +9,44 @@ extension Publisher {
/// Wraps this publisher with a type eraser.
///
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher` to
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher`` to
/// the downstream subscriber, rather than this publishers actual type.
/// This form of _type erasure_ preserves abstraction across API boundaries, such as
/// different modules.
/// When you expose your publishers as the `AnyPublisher` type, you can change
/// the underlying implementation over time without affecting existing clients.
///
/// The following example shows two types that each have a `publisher` property.
/// `TypeWithSubject` exposes this property as its actual type, `PassthroughSubject`,
/// while `TypeWithErasedSubject` uses `eraseToAnyPublisher()` to expose it as
/// an `AnyPublisher`. As seen in the output, a caller from another module can access
/// `TypeWithSubject.publisher` as its native type. This means you cant change your
/// publisher to a different type without breaking the caller. By comparison,
/// `TypeWithErasedSubject.publisher` appears to callers as an `AnyPublisher`, so you
/// can change the underlying publisher type at will.
///
/// public class TypeWithSubject {
/// public let publisher: some Publisher = PassthroughSubject<Int,Never>()
/// }
/// public class TypeWithErasedSubject {
/// public let publisher: some Publisher = PassthroughSubject<Int,Never>()
/// .eraseToAnyPublisher()
/// }
///
/// // In another module:
/// let nonErased = TypeWithSubject()
/// if let subject = nonErased.publisher as? PassthroughSubject<Int,Never> {
/// print("Successfully cast nonErased.publisher.")
/// }
/// let erased = TypeWithErasedSubject()
/// if let subject = erased.publisher as? PassthroughSubject<Int,Never> {
/// print("Successfully cast erased.publisher.")
/// }
///
/// // Prints "Successfully cast nonErased.publisher."
///
/// - Returns: An ``AnyPublisher`` wrapping this publisher.
@inlinable
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
return .init(self)
}
@@ -19,7 +55,13 @@ extension Publisher {
/// A type-erasing publisher.
///
/// Use `AnyPublisher` to wrap a publisher whose type has details you dont want to expose
/// to subscribers or other publishers.
/// across API boundaries, such as different modules. Wrapping a `Subject` with
/// `AnyPublisher` also prevents callers from accessing its `send(_:)` method. When you
/// use type erasure this way, you can change the underlying publisher implementation over
/// time without affecting existing clients.
///
/// You can use OpenCombines `eraseToAnyPublisher()` operator to wrap a publisher with
/// `AnyPublisher`.
public struct AnyPublisher<Output, Failure: Error>
: CustomStringConvertible,
CustomPlaygroundDisplayConvertible
@@ -29,13 +71,17 @@ public struct AnyPublisher<Output, Failure: Error>
/// Creates a type-erasing publisher to wrap the provided publisher.
///
/// - Parameters:
/// - publisher: A publisher to wrap with a type-eraser.
/// - Parameter publisher: A publisher to wrap with a type-eraser.
@inlinable
public init<PublisherType: Publisher>(_ publisher: PublisherType)
where Output == PublisherType.Output, Failure == PublisherType.Failure
{
box = PublisherBox(base: publisher)
// If this has already been boxed, avoid boxing again
if let erased = publisher as? AnyPublisher<Output, Failure> {
box = erased.box
} else {
box = PublisherBox(base: publisher)
}
}
public var description: String {
@@ -60,7 +106,7 @@ extension AnyPublisher: Publisher {
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
box.subscribe(subscriber)
box.receive(subscriber: subscriber)
}
}
@@ -97,6 +143,6 @@ internal final class PublisherBox<PublisherType: Publisher>
override internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
base.subscribe(subscriber)
base.receive(subscriber: subscriber)
}
}
+11 -5
View File
@@ -7,9 +7,10 @@
/// A type-erasing subscriber.
///
/// Use an `AnySubscriber` to wrap an existing subscriber whose details you dont want
/// to expose. You can also use `AnySubscriber` to create a custom subscriber by providing
/// closures for `Subscriber`s methods, rather than implementing `Subscriber` directly.
/// Use an `AnySubscriber` to wrap an existing subscriber whose details you dont want to
/// expose. You can also use `AnySubscriber` to create a custom subscriber by providing
/// closures for the methods defined in `Subscriber`, rather than implementing
/// `Subscriber` directly.
public struct AnySubscriber<Input, Failure: Error>: Subscriber,
CustomStringConvertible,
CustomReflectable,
@@ -44,6 +45,11 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
where Input == Subscriber.Input, Failure == Subscriber.Failure
{
if let erased = subscriber as? AnySubscriber<Input, Failure> {
self = erased
return
}
combineIdentifier = subscriber.combineIdentifier
box = AnySubscriberBox(subscriber)
@@ -62,8 +68,8 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
} else if let desccription = subscriber as? CustomStringConvertible {
playgroundDescriptionThunk = { desccription.description }
} else if let description = subscriber as? CustomStringConvertible {
playgroundDescriptionThunk = { description.description }
} else {
let fixedDescription = String(describing: type(of: subscriber))
playgroundDescriptionThunk = { fixedDescription }
+7 -7
View File
@@ -5,7 +5,7 @@
// Created by Sergej Jaskiewicz on 10.06.2019.
//
/// A protocol indicating that an activity or action may be canceled.
/// A protocol indicating that an activity or action supports cancellation.
///
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
/// as timers, network access, or disk I/O.
@@ -17,18 +17,18 @@ public protocol Cancellable {
extension Cancellable {
/// Stores this Cancellable in the specified collection.
/// Parameters:
/// - collection: The collection to store this Cancellable.
/// Stores this cancellable instance in the specified collection.
///
/// - Parameter collection: The collection in which to store this `Cancellable`.
public func store<Cancellables: RangeReplaceableCollection>(
in collection: inout Cancellables
) where Cancellables.Element == AnyCancellable {
AnyCancellable(self).store(in: &collection)
}
/// Stores this Cancellable in the specified set.
/// Parameters:
/// - collection: The set to store this Cancellable.
/// Stores this cancellable instance in the specified set.
///
/// - Parameter set: The set in which to store this `Cancellable`.
public func store(in set: inout Set<AnyCancellable>) {
AnyCancellable(self).store(in: &set)
}
+8
View File
@@ -5,17 +5,25 @@
// Created by Sergej Jaskiewicz on 11.06.2019.
//
/// A type that defines methods for decoding.
public protocol TopLevelDecoder {
/// The type this decoder accepts.
associatedtype Input
/// Decodes an instance of the indicated type.
func decode<DecodablyType: Decodable>(_ type: DecodablyType.Type,
from: Input) throws -> DecodablyType
}
/// A type that defines methods for encoding.
public protocol TopLevelEncoder {
/// The type this encoder produces.
associatedtype Output
/// Encodes an instance of the indicated type.
///
/// - Parameter value: The instance to encode.
func encode<EncodableType: Encodable>(_ value: EncodableType) throws -> Output
}
+25 -5
View File
@@ -5,21 +5,41 @@
// Created by Sergej Jaskiewicz on 10.06.2019.
//
import func COpenCombineHelpers.nextCombineIdentifier
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
#if WASI
private var __identifier: UInt64 = 0
internal func __nextCombineIdentifier() -> UInt64 {
defer { __identifier += 1 }
return __identifier
}
#endif // WASI
/// A unique identifier for identifying publisher streams.
///
/// To conform to `CustomCombineIdentifierConvertible` in a
/// `Subscription` or `Subject` that you implement as a structure, create an instance of
/// `CombineIdentifier` as follows:
///
/// let combineIdentifier = CombineIdentifier()
public struct CombineIdentifier: Hashable, CustomStringConvertible {
private let id: UInt64
private let rawValue: UInt64
/// Creates a unique Combine identifier.
public init() {
self.id = nextCombineIdentifier()
rawValue = __nextCombineIdentifier()
}
/// Creates a Combine identifier, using the bit pattern of the provided object.
public init(_ obj: AnyObject) {
id = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
rawValue = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
public var description: String {
return "0x\(String(id, radix: 16))"
return "0x\(String(rawValue, radix: 16))"
}
}
@@ -7,13 +7,18 @@
/// A publisher that provides an explicit means of connecting and canceling publication.
///
/// Use a `ConnectablePublisher` when you need to perform additional configuration or
/// setup prior to producing any elements.
///
/// This publisher doesnt produce any elements until you call its `connect()` method.
///
/// Use `makeConnectable()` to create a `ConnectablePublisher` from any publisher whose
/// failure type is `Never`.
public protocol ConnectablePublisher: Publisher {
/// Connects to the publisher and returns a `Cancellable` instance with which
/// to cancel publishing.
/// Connects to the publisher, allowing it to produce elements, and returns
/// an instance with which to cancel publishing.
///
/// - Returns: A `Cancellable` instance that can be used to cancel publishing.
/// - Returns: A `Cancellable` instance that you use to cancel publishing.
func connect() -> Cancellable
}
+185 -87
View File
@@ -5,32 +5,39 @@
// 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.
///
/// Unlike `PassthroughSubject`, `CurrentValueSubject` maintains a buffer of the most
/// recently published element.
///
/// Calling `send(_:)` on a `CurrentValueSubject` also updates the current value, making
/// it equivalent to updating the `value` directly.
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
private let _lock = UnfairRecursiveLock.allocate()
private let lock = UnfairLock.allocate()
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
private var active = true
private var _value: Output
private var completion: Subscribers.Completion<Failure>?
private var _completion: Subscribers.Completion<Failure>?
private var downstreams = ConduitList<Output, Failure>.empty
internal var upstreamSubscriptions: [Subscription] = []
private var currentValue: Output
internal var hasAnyDownstreamDemand = false
private var upstreamSubscriptions: [Subscription] = []
/// The value wrapped by this subject, published as a new element whenever it changes.
public var value: Output {
get {
return _value
lock.lock()
defer { lock.unlock() }
return currentValue
}
set {
send(newValue)
lock.lock()
currentValue = newValue
sendValueAndConsumeLock(newValue)
}
}
@@ -38,122 +45,213 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
///
/// - Parameter value: The initial value to publish.
public init(_ value: Output) {
self._value = value
self.currentValue = value
}
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
for subscription in upstreamSubscriptions {
subscription.cancel()
}
_lock.deallocate()
lock.deallocate()
}
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
subscription.request(.unlimited)
}
lock.lock()
upstreamSubscriptions.append(subscription)
lock.unlock()
subscription.request(.unlimited)
}
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Output == Subscriber.Input, Failure == Subscriber.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
lock.lock()
if active {
let conduit = Conduit(parent: self, downstream: subscriber)
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
} else {
let completion = self.completion!
lock.unlock()
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
}
}
public func send(_ input: Output) {
_lock.do {
_value = input
for subscription in _subscriptions where !subscription.isCompleted {
if subscription._demand > 0 {
subscription._offer(input)
subscription._demand -= 1
} else {
subscription._delivered = false
}
}
lock.lock()
sendValueAndConsumeLock(input)
}
private func sendValueAndConsumeLock(_ newValue: Output) {
#if DEBUG
lock.assertOwner()
#endif
guard active else {
lock.unlock()
return
}
currentValue = newValue
let downstreams = self.downstreams
lock.unlock()
downstreams.forEach { conduit in
conduit.offer(newValue)
}
}
public func send(completion: Subscribers.Completion<Failure>) {
_completion = completion
_lock.do {
for subscriber in _subscriptions {
subscriber._receive(completion: completion)
}
lock.lock()
guard active else {
lock.unlock()
return
}
active = false
self.completion = completion
let downstreams = self.downstreams
self.downstreams.removeAll()
lock.unlock()
downstreams.forEach { conduit in
conduit.finish(completion: completion)
}
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
guard active else {
lock.unlock()
return
}
downstreams.remove(conduit)
lock.unlock()
}
}
extension CurrentValueSubject {
fileprivate class Conduit: Subscription {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
fileprivate var _parent: CurrentValueSubject?
fileprivate var parent: CurrentValueSubject?
fileprivate var _downstream: AnySubscriber<Output, Failure>?
fileprivate var downstream: Downstream?
fileprivate var _demand: Subscribers.Demand = .none
fileprivate var demand = Subscribers.Demand.none
/// Whethere we satisfied the demand
fileprivate var _delivered = false
private var lock = UnfairLock.allocate()
var isCompleted: Bool {
return _parent == nil
}
private var downstreamLock = UnfairRecursiveLock.allocate()
fileprivate func _offer(_ value: Output) {
let newDemand = _downstream?.receive(value) ?? .none
_demand += newDemand
_delivered = true
}
private var deliveredCurrentValue = false
fileprivate init(parent: CurrentValueSubject,
downstream: AnySubscriber<Output, Failure>) {
_parent = parent
_downstream = downstream
downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
override func offer(_ output: Output) {
lock.lock()
guard demand > 0, let downstream = self.downstream else {
deliveredCurrentValue = false
lock.unlock()
return
}
demand -= 1
deliveredCurrentValue = true
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(output)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
demand += newDemand
lock.unlock()
}
func request(_ demand: Subscribers.Demand) {
precondition(demand > 0)
_parent?._lock.do {
if !_delivered, let value = _parent?.value {
_offer(value)
_demand += demand
_demand -= 1
} else {
_demand = demand
}
_parent?.hasAnyDownstreamDemand = true
override func finish(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func cancel() {
_parent = nil
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
if deliveredCurrentValue {
self.demand += demand
lock.unlock()
return
}
// Hasn't yet delivered the current value
self.demand += demand
deliveredCurrentValue = true
if let currentValue = self.parent?.value {
self.demand -= 1
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(currentValue)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
self.demand += newDemand
}
lock.unlock()
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "CurrentValueSubject" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("demand", demand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension CurrentValueSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "CurrentValueSubject" }
}
@@ -5,8 +5,18 @@
// Created by Sergej Jaskiewicz on 10.06.2019.
//
/// A protocol for uniquely identifying publisher streams.
///
/// If you create a custom `Subscription` or `Subscriber` type, implement this protocol
/// so that development tools can uniquely identify publisher chains in your app.
/// If your type is a class, OpenCombine provides an implementation of `combineIdentifier`
/// for you.
/// If your type is a structure, set up the identifier as follows:
///
/// let combineIdentifier = CombineIdentifier()
public protocol CustomCombineIdentifierConvertible {
/// A unique identifier for identifying publisher streams.
var combineIdentifier: CombineIdentifier { get }
}
+212
View File
@@ -0,0 +1,212 @@
//
// Future.swift
//
//
// Created by Max Desiatov on 24/11/2019.
//
/// A publisher that eventually produces a single value and then finishes or fails.
public final class Future<Output, Failure: Error>: Publisher {
/// A type that represents a closure to invoke in the future, when an element or error
/// is available.
///
/// The promise closure receives one parameter: a `Result` that contains either
/// a single element published by a `Future`, or an error.
public typealias Promise = (Result<Output, Failure>) -> Void
private let lock = UnfairLock.allocate()
private var downstreams = ConduitList<Output, Failure>.empty
private var result: Result<Output, Failure>?
/// Creates a publisher that invokes a promise closure when the publisher emits
/// an element.
///
/// - Parameter attemptToFulfill: A `Promise` that the publisher invokes when
/// the publisher emits an element or terminates with an error.
public init(
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
) {
attemptToFulfill(self.promise)
}
deinit {
lock.deallocate()
}
private func promise(_ result: Result<Output, Failure>) {
lock.lock()
guard self.result == nil else {
lock.unlock()
return
}
self.result = result
let downstreams = self.downstreams
self.downstreams.removeAll()
lock.unlock()
switch result {
case .success(let output):
downstreams.forEach { $0.offer(output) }
case .failure(let error):
downstreams.forEach { $0.finish(completion: .failure(error)) }
}
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
let conduit = Conduit(parent: self, downstream: subscriber)
lock.lock()
if let result = self.result {
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
conduit.fulfill(result)
} else {
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
}
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
downstreams.remove(conduit)
lock.unlock()
}
}
extension Future {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
fileprivate var parent: Future?
fileprivate var downstream: Downstream?
fileprivate var hasAnyDemand = false
private var lock = UnfairLock.allocate()
private var downstreamLock = UnfairRecursiveLock.allocate()
fileprivate init(parent: Future, downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
fileprivate func fulfill(_ result: Result<Output, Failure>) {
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
let parent = self.parent
if case .success = result, !hasAnyDemand {
lock.unlock()
return
}
self.downstream = nil
self.parent = nil
lock.unlock()
downstreamLock.lock()
switch result {
case .success(let output):
_ = downstream.receive(output)
downstream.receive(completion: .finished)
case .failure(let error):
downstream.receive(completion: .failure(error))
}
downstreamLock.unlock()
parent?.disassociate(self)
}
override func offer(_ output: Output) {
fulfill(.success(output))
}
override func finish(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
assertionFailure("unreachable")
case .failure(let error):
fulfill(.failure(error))
}
}
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard let downstream = self.downstream, let parent = self.parent else {
lock.unlock()
return
}
hasAnyDemand = true
parent.lock.lock()
guard let result = parent.result else {
parent.lock.unlock()
lock.unlock()
return
}
parent.lock.unlock()
self.downstream = nil
self.parent = nil
lock.unlock()
downstreamLock.lock()
switch result {
case .success(let output):
_ = downstream.receive(output)
downstream.receive(completion: .finished)
case .failure(let error):
// This branch is not reachable under normal circumstances,
// but may be reachable in case of a race condition.
downstream.receive(completion: .failure(error))
}
downstreamLock.unlock()
parent.disassociate(self)
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "Future" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("hasAnyDemand", hasAnyDemand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,205 @@
//
// AbstractCombineLatest.swift
//
//
// Created by Sergej Jaskiewicz on 10.12.2019.
//
internal class AbstractCombineLatest<Output, Failure, Downstream: Subscriber>
where Downstream.Input == Output, Downstream.Failure == Failure
{
private let downstream: Downstream
// TODO: The size of these arrays always stays the same.
// Maybe we can leverage ManagedBuffer/ManagedBufferPointer here
// to avoid additional allocations.
private var buffers: [Any?] // 0x78
private var subscriptions: [Subscription?] // 0x80
private var demand = Subscribers.Demand.none // 0x88
private var recursion = false // 0x90
private var finished = false // 0x98
private var errored = false // 0xA0
private var cancelled = false // 0xA8
private let upstreamCount: Int // 0xB0
private var finishCount = 0 // 0xB8
private let lock = UnfairLock.allocate() // 0xC0
private let downstreamLock = UnfairRecursiveLock.allocate() // 0xC8
internal init(downstream: Downstream, upstreamCount: Int) {
self.downstream = downstream
self.buffers = Array(repeating: nil, count: upstreamCount)
self.subscriptions = Array(repeating: nil, count: upstreamCount)
self.upstreamCount = upstreamCount
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
// TODO: There should be more type-safe (and faster) way.
// E. g. what if we store `buffers` in subclasses?
internal func convert(values: [Any?]) -> Output {
abstractMethod()
}
fileprivate final func receive(subscription: Subscription, index: Int) {
lock.lock()
guard !cancelled && subscriptions[index] == nil else {
lock.unlock()
subscription.cancel()
return
}
subscriptions[index] = subscription
lock.unlock()
}
fileprivate final func receive(_ input: Any, index: Int) -> Subscribers.Demand {
lock.lock()
if cancelled || finished {
lock.unlock()
return .none
}
buffers[index] = input
guard !recursion && demand > 0 && buffers.allSatisfy({ $0 != nil }) else {
lock.unlock()
return .none
}
demand -= 1
recursion = true
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(convert(values: buffers))
downstreamLock.unlock()
lock.lock()
recursion = false
demand += newDemand
lock.unlock()
return .none
}
fileprivate final func receive(completion: Subscribers.Completion<Failure>,
index: Int) {
switch completion {
case .finished:
lock.lock()
if finished {
lock.unlock()
return
}
finishCount += 1
subscriptions[index] = nil
if finishCount == upstreamCount {
finished = true
buffers = Array(repeating: nil, count: upstreamCount)
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
} else {
lock.unlock()
}
case .failure:
lock.lock()
finished = true
errored = true
let subscriptions = self.subscriptions
self.subscriptions = Array(repeating: nil, count: upstreamCount)
buffers = Array(repeating: nil, count: upstreamCount)
lock.unlock()
for (i, subscription) in subscriptions.enumerated() where i != index {
subscription?.cancel()
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
}
}
extension AbstractCombineLatest: Subscription {
internal func request(_ demand: Subscribers.Demand) {
demand.assertNonZero() // TODO: Test this
lock.lock()
guard !cancelled && !finished else {
lock.unlock()
return
}
self.demand += demand
lock.unlock()
for subscription in subscriptions {
subscription?.request(demand)
}
}
internal func cancel() {
lock.lock()
cancelled = true
let subscriptions = self.subscriptions
self.subscriptions = Array(repeating: nil, count: upstreamCount)
buffers = Array(repeating: nil, count: upstreamCount)
lock.unlock()
for subscription in subscriptions {
subscription?.cancel()
}
}
}
extension AbstractCombineLatest: CustomStringConvertible {
internal var description: String { return "CombineLatest" }
}
extension AbstractCombineLatest: CustomReflectable {
internal var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("downstream", downstream),
("upstreamSubscriptions", subscriptions),
("demand", demand),
("buffers", buffers)
]
return Mirror(self, children: children)
}
}
extension AbstractCombineLatest: CustomPlaygroundDisplayConvertible {
internal final var playgroundDescription: Any { return description }
}
extension AbstractCombineLatest {
internal struct Side<Input>: Subscriber, CustomStringConvertible {
private let index: Int
private let combiner: AbstractCombineLatest
internal let combineIdentifier = CombineIdentifier()
internal init(index: Int, combiner: AbstractCombineLatest) {
self.index = index
self.combiner = combiner
}
internal func receive(subscription: Subscription) {
combiner.receive(subscription: subscription, index: index)
}
internal func receive(_ input: Input) -> Subscribers.Demand {
return combiner.receive(input, index: index)
}
internal func receive(completion: Subscribers.Completion<Failure>) {
combiner.receive(completion: completion, index: index)
}
internal var description: String { return "CombineLatest" }
}
}
@@ -0,0 +1,40 @@
//
// ConduitBase.swift
//
//
// Created by Sergej Jaskiewicz on 25.06.2020.
//
internal class ConduitBase<Output, Failure: Error>: Subscription {
internal init() {}
internal func offer(_ output: Output) {
abstractMethod()
}
internal func finish(completion: Subscribers.Completion<Failure>) {
abstractMethod()
}
internal func request(_ demand: Subscribers.Demand) {
abstractMethod()
}
internal func cancel() {
abstractMethod()
}
}
extension ConduitBase: Equatable {
internal static func == (lhs: ConduitBase<Output, Failure>,
rhs: ConduitBase<Output, Failure>) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
}
extension ConduitBase: Hashable {
internal func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
@@ -0,0 +1,57 @@
//
// ConduitList.swift
//
//
// Created by Sergej Jaskiewicz on 25.06.2020.
//
internal enum ConduitList<Output, Failure: Error> {
case empty
case single(ConduitBase<Output, Failure>)
case many(Set<ConduitBase<Output, Failure>>)
}
extension ConduitList {
internal mutating func insert(_ conduit: ConduitBase<Output, Failure>) {
switch self {
case .empty:
self = .single(conduit)
case .single(conduit):
break // This element already exists.
case .single(let existingConduit):
self = .many([existingConduit, conduit])
case .many(var set):
set.insert(conduit)
self = .many(set)
}
}
internal func forEach(
_ body: (ConduitBase<Output, Failure>) throws -> Void
) rethrows {
switch self {
case .empty:
break
case .single(let conduit):
try body(conduit)
case .many(let set):
try set.forEach(body)
}
}
internal mutating func remove(_ conduit: ConduitBase<Output, Failure>) {
switch self {
case .single(conduit):
self = .empty
case .empty, .single:
break
case .many(var set):
set.remove(conduit)
self = .many(set)
}
}
internal mutating func removeAll() {
self = .empty
}
}
+177
View File
@@ -0,0 +1,177 @@
//
// DebugHook.swift
//
//
// Created by Sergej Jaskiewicz on 27.09.2020.
//
internal final class DebugHook {
private struct Handler: Hashable {
let handler: _Introspection
static func == (lhs: Handler, rhs: Handler) -> Bool {
return lhs.handler === rhs.handler
}
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(handler))
}
}
internal static func getGlobalHook() -> DebugHook? {
globalLock.lock()
defer { globalLock.unlock() }
return globalHook
}
internal static func enable(_ handler: _Introspection) {
let hook: DebugHook
DebugHook.globalLock.lock()
defer { DebugHook.globalLock.unlock() }
if let _hook = DebugHook.globalHook {
hook = _hook
} else {
hook = DebugHook()
DebugHook.globalHook = hook
}
hook.lock.lock()
defer { hook.lock.unlock() }
hook.handlers.insert(Handler(handler: handler))
}
internal static func disable(_ handler: _Introspection) {
DebugHook.globalLock.lock()
defer { DebugHook.globalLock.unlock() }
guard let hook = DebugHook.globalHook else { return }
hook.lock.lock()
hook.handlers.remove(Handler(handler: handler))
let noMoreHandlers = hook.handlers.isEmpty
hook.lock.unlock()
if noMoreHandlers {
DebugHook.globalHook = nil
}
}
internal static func handlerIsEnabled(_ handler: _Introspection) -> Bool {
DebugHook.globalLock.lock()
defer { DebugHook.globalLock.unlock() }
guard let hook = DebugHook.globalHook else { return false }
hook.lock.lock()
defer { hook.lock.unlock() }
return hook.handlers.contains(Handler(handler: handler))
}
private static var globalHook: DebugHook?
private static let globalLock = UnfairLock.allocate()
private let lock = UnfairLock.allocate()
private var handlers = Set<Handler>()
internal var debugHandlers: [_Introspection] {
lock.lock()
defer { lock.unlock() }
return handlers.map { $0.handler }
}
private init() {}
deinit {
lock.deallocate()
}
internal func willReceive<Upstream: Publisher, Downstream: Subscriber>(
publisher: Upstream,
subscriber: Downstream
) where Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
for debugHandler in debugHandlers {
debugHandler.willReceive(publisher: publisher, subscriber: subscriber)
}
}
internal func didReceive<Upstream: Publisher, Downstream: Subscriber>(
publisher: Upstream,
subscriber: Downstream
) where Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
for debugHandler in debugHandlers {
debugHandler.didReceive(publisher: publisher, subscriber: subscriber)
}
}
internal func willReceive<Downstream: Subscriber>(subscriber: Downstream,
subscription: Subscription) {
for debugHandler in debugHandlers {
debugHandler.willReceive(subscriber: subscriber, subscription: subscription)
}
}
internal func didReceive<Downstream: Subscriber>(subscriber: Downstream,
subscription: Subscription) {
for debugHandler in debugHandlers {
debugHandler.didReceive(subscriber: subscriber, subscription: subscription)
}
}
internal func willReceive<Downstream: Subscriber>(subscriber: Downstream,
input: Downstream.Input) {
for debugHandler in debugHandlers {
debugHandler.willReceive(subscriber: subscriber, input: input)
}
}
internal func didReceive<Downstream: Subscriber>(
subscriber: Downstream,
input: Downstream.Input,
resultingDemand: Subscribers.Demand
) {
for debugHandler in debugHandlers {
debugHandler.didReceive(subscriber: subscriber,
input: input,
resultingDemand: resultingDemand)
}
}
internal func willReceive<Downstream: Subscriber>(
subscriber: Downstream,
completion: Subscribers.Completion<Downstream.Failure>
) {
for debugHandler in debugHandlers {
debugHandler.willReceive(subscriber: subscriber, completion: completion)
}
}
internal func didReceive<Downstream: Subscriber>(
subscriber: Downstream,
completion: Subscribers.Completion<Downstream.Failure>
) {
for debugHandler in debugHandlers {
debugHandler.didReceive(subscriber: subscriber, completion: completion)
}
}
internal func willRequest(subscription: Subscription, demand: Subscribers.Demand) {
for debugHandler in debugHandlers {
debugHandler.willRequest(subscription: subscription, demand)
}
}
internal func didRequest(subscription: Subscription, demand: Subscribers.Demand) {
for debugHandler in debugHandlers {
debugHandler.didRequest(subscription: subscription, demand)
}
}
internal func willCancel(subscription: Subscription) {
for debugHandler in debugHandlers {
debugHandler.willCancel(subscription: subscription)
}
}
internal func didCancel(subscription: Subscription) {
for debugHandler in debugHandlers {
debugHandler.didCancel(subscription: subscription)
}
}
}
@@ -0,0 +1,174 @@
//
// FilterProducer.swift
//
//
// Created by Sergej Jaskiewicz on 23.10.2019.
//
/// A helper class that acts like both subscriber and subscription.
///
/// Filter-like operators send an instance of their `Inner` class that is subclass
/// of this class to the upstream publisher (as subscriber) and
/// to the downstream subscriber (as subscription).
///
/// Filter-like operators include `Publishers.Filter`,
/// `Publishers.RemoveDuplicates`, `Publishers.PrefixWhile` and more.
///
/// Subclasses must override the `receive(newValue:)` and `description`.
internal class FilterProducer<Downstream: Subscriber,
Input,
Output,
UpstreamFailure: Error,
Filter>
: CustomStringConvertible,
CustomReflectable
where Downstream.Input == Output
{
// MARK: - State
private enum State {
case awaitingSubscription
case connected(Subscription)
case completed
}
internal final let filter: Filter
internal final let downstream: Downstream
private let lock = UnfairLock.allocate()
private var state = State.awaitingSubscription
internal init(downstream: Downstream, filter: Filter) {
self.downstream = downstream
self.filter = filter
}
deinit {
lock.deallocate()
}
// MARK: - Abstract methods
internal func receive(
newValue: Input
) -> PartialCompletion<Output?, Downstream.Failure> {
abstractMethod()
}
internal var description: String {
abstractMethod()
}
// MARK: - CustomReflectable
internal var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
return Mirror(self, children: children)
}
}
extension FilterProducer: Subscriber {
internal func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .connected(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
internal func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
switch state {
case .awaitingSubscription:
lock.unlock()
fatalError("Invalid state: Received value before receiving subscription")
case .completed:
lock.unlock()
case let .connected(subscription):
lock.unlock()
switch receive(newValue: input) {
case let .continue(output?):
return downstream.receive(output)
case .continue(nil):
return .max(1)
case .finished:
lock.lock()
state = .completed
lock.unlock()
subscription.cancel()
downstream.receive(completion: .finished)
case let .failure(error):
lock.lock()
state = .completed
lock.unlock()
subscription.cancel()
downstream.receive(completion: .failure(error))
}
}
return .none
}
internal func receive(completion: Subscribers.Completion<UpstreamFailure>) {
lock.lock()
switch state {
case .awaitingSubscription:
lock.unlock()
fatalError("Invalid state: Received completion before receiving subscription")
case .completed:
lock.unlock()
return
case .connected:
state = .completed
lock.unlock()
switch completion {
case .finished:
downstream.receive(completion: .finished)
case let .failure(failure):
downstream.receive(completion: .failure(failure as! Downstream.Failure))
}
}
}
}
extension FilterProducer: Subscription {
internal func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .awaitingSubscription:
lock.unlock()
fatalError("Invalid state: Received request before sending subscription")
case .completed:
lock.unlock()
return
case let .connected(subscription):
lock.unlock()
subscription.request(demand)
}
}
internal func cancel() {
lock.lock()
guard case let .connected(subscription) = state else {
state = .completed
lock.unlock()
return
}
state = .completed
lock.unlock()
subscription.cancel()
}
}
extension FilterProducer: CustomPlaygroundDisplayConvertible {
internal var playgroundDescription: Any { return description }
}
+18 -16
View File
@@ -5,24 +5,26 @@
// Created by Sergej Jaskiewicz on 11.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension UnfairLock {
@inlinable
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
lock()
defer { unlock() }
return try body()
}
#if WASI
internal struct __UnfairLock { // swiftlint:disable:this type_name
internal static func allocate() -> UnfairLock { return .init() }
internal func lock() {}
internal func unlock() {}
internal func assertOwner() {}
internal func deallocate() {}
}
extension UnfairRecursiveLock {
@inlinable
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
lock()
defer { unlock() }
return try body()
}
internal struct __UnfairRecursiveLock { // swiftlint:disable:this type_name
internal static func allocate() -> UnfairRecursiveLock { return .init() }
internal func lock() {}
internal func unlock() {}
internal func deallocate() {}
}
#endif // WASI
internal typealias UnfairLock = __UnfairLock
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
@@ -0,0 +1,212 @@
//
// PublishedSubject.swift
//
//
// Created by Sergej Jaskiewicz on 29.10.2020.
//
internal final class PublishedSubject<Output>: Subject {
internal typealias Failure = Never
private let lock = UnfairLock.allocate()
private var downstreams = ConduitList<Output, Failure>.empty
private var currentValue: Output
private var upstreamSubscriptions: [Subscription] = []
private var hasAnyDownstreamDemand = false
private var changePublisher: ObservableObjectPublisher?
internal var value: Output {
get {
lock.lock()
defer { lock.unlock() }
return currentValue
}
set {
send(newValue)
}
}
internal var objectWillChange: ObservableObjectPublisher? {
get {
lock.lock()
defer { lock.unlock() }
return changePublisher
}
set {
lock.lock()
defer { lock.unlock() }
changePublisher = newValue
}
}
internal init(_ value: Output) {
self.currentValue = value
}
deinit {
for subscription in upstreamSubscriptions {
subscription.cancel()
}
lock.deallocate()
}
internal func send(subscription: Subscription) {
lock.lock()
upstreamSubscriptions.append(subscription)
lock.unlock()
subscription.request(.unlimited)
}
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Never
{
lock.lock()
let conduit = Conduit(parent: self, downstream: subscriber)
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
}
internal func send(_ input: Output) {
lock.lock()
let downstreams = self.downstreams
let changePublisher = self.changePublisher
lock.unlock()
changePublisher?.send()
downstreams.forEach { conduit in
conduit.offer(input)
}
lock.lock()
currentValue = input
lock.unlock()
}
internal func send(completion: Subscribers.Completion<Never>) {
fatalError("unreachable")
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
downstreams.remove(conduit)
lock.unlock()
}
}
extension PublishedSubject {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Never
{
fileprivate var parent: PublishedSubject?
fileprivate var downstream: Downstream?
fileprivate var demand = Subscribers.Demand.none
private var lock = UnfairLock.allocate()
private var downstreamLock = UnfairRecursiveLock.allocate()
private var deliveredCurrentValue = false
fileprivate init(parent: PublishedSubject,
downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
override func offer(_ output: Output) {
lock.lock()
guard demand > 0, let downstream = self.downstream else {
deliveredCurrentValue = false
lock.unlock()
return
}
demand -= 1
deliveredCurrentValue = true
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(output)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
demand += newDemand
lock.unlock()
}
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
if deliveredCurrentValue {
self.demand += demand
lock.unlock()
return
}
// Hasn't yet delivered the current value
self.demand += demand
deliveredCurrentValue = true
if let currentValue = self.parent?.value {
self.demand -= 1
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(currentValue)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
self.demand += newDemand
}
lock.unlock()
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "PublishedSubject" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("demand", demand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,32 @@
//
// PublishedSubscriber.swift
//
//
// Created by Sergej Jaskiewicz on 29.10.2020.
//
internal struct PublishedSubscriber<Value>: Subscriber {
internal typealias Input = Value
internal typealias Failure = Never
internal let combineIdentifier = CombineIdentifier()
private weak var subject: PublishedSubject<Value>?
internal init(_ subject: PublishedSubject<Value>) {
self.subject = subject
}
internal func receive(subscription: Subscription) {
subject?.send(subscription: subscription)
}
internal func receive(_ input: Value) -> Subscribers.Demand {
subject?.send(input)
return .none
}
internal func receive(completion: Subscribers.Completion<Never>) {}
}
@@ -5,13 +5,11 @@
// 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).
/// to the downstream subscriber (as subscription).
///
/// Reduce-like operators include `Publishers.Reduce`, `Publishers.TryReduce`,
/// `Publishers.Count`, `Publishers.FirstWhere`, `Publishers.AllSatisfy` and more.
@@ -50,8 +48,6 @@ internal class ReduceProducer<Downstream: Subscriber,
private var upstreamCompleted = false
private var empty = true
internal init(downstream: Downstream, initial: Output?, reduce: Reducer) {
self.downstream = downstream
self.initial = initial
@@ -102,7 +98,9 @@ internal class ReduceProducer<Downstream: Subscriber,
return
}
upstreamCompleted = true
self.completed = downstreamRequested || empty
if downstreamRequested {
self.completed = true
}
let completed = self.completed
let result = self.result
lock.unlock()
@@ -159,7 +157,6 @@ extension ReduceProducer: Subscriber {
lock.unlock()
return .none
}
empty = false
lock.unlock()
// Combine doesn't hold the lock when calling `receive(newValue:)`.
@@ -5,9 +5,6 @@
// 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,
@@ -16,7 +13,7 @@ internal final class SubjectSubscriber<Downstream: Subject>
Subscription
{
private let lock = UnfairLock.allocate()
private var downstreamSubject: Downstream?
private weak var downstreamSubject: Downstream?
private var upstreamSubscription: Subscription?
private var isCancelled: Bool { return downstreamSubject == nil }
@@ -42,23 +39,21 @@ internal final class SubjectSubscriber<Downstream: Subject>
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
lock.lock()
guard let downstreamSubject = downstreamSubject else {
guard let subject = downstreamSubject, upstreamSubscription != nil else {
lock.unlock()
return .none
}
guard upstreamSubscription != nil else { APIViolationValueBeforeSubscription() }
lock.unlock()
downstreamSubject.send(input)
subject.send(input)
return .none
}
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
lock.lock()
guard let subject = downstreamSubject else {
guard let subject = downstreamSubject, upstreamSubscription != nil else {
lock.unlock()
return
}
guard upstreamSubscription != nil else { APIViolationUnexpectedCompletion() }
lock.unlock()
subject.send(completion: completion)
downstreamSubject = nil
@@ -89,11 +84,7 @@ internal final class SubjectSubscriber<Downstream: Subject>
internal func cancel() {
lock.lock()
if isCancelled {
lock.unlock()
return
}
guard let subscription = upstreamSubscription else {
guard !isCancelled, let subscription = upstreamSubscription else {
lock.unlock()
return
}
@@ -0,0 +1,69 @@
//
// SubscriberTap.swift
//
//
// Created by Sergej Jaskiewicz on 27.09.2020.
//
internal protocol SubscriberTapMarker {
var inner: Any { mutating get }
}
internal struct SubscriberTap<Subscriber: OpenCombine.Subscriber>
: OpenCombine.Subscriber,
CustomStringConvertible,
SubscriberTapMarker
{
internal typealias Input = Subscriber.Input
internal typealias Failure = Subscriber.Failure
private let subscriber: Subscriber
internal lazy var inner: Any = AnySubscriber(self.subscriber)
internal init(subscriber: Subscriber) {
self.subscriber = subscriber
}
internal var combineIdentifier: CombineIdentifier {
return subscriber.combineIdentifier
}
internal func receive(subscription: Subscription) {
let hook = DebugHook.getGlobalHook()
if let subscriptionTap = subscription as? SubscriptionTap {
hook?.willReceive(subscriber: subscriber,
subscription: subscriptionTap.subscription)
subscriber.receive(subscription: subscriptionTap)
hook?.didReceive(subscriber: subscriber,
subscription: subscriptionTap.subscription)
} else {
hook?.willReceive(subscriber: subscriber, subscription: subscription)
subscriber
.receive(subscription: SubscriptionTap(subscription: subscription))
hook?.didReceive(subscriber: subscriber, subscription: subscription)
}
}
internal func receive(_ input: Input) -> Subscribers.Demand {
let hook = DebugHook.getGlobalHook()
hook?.willReceive(subscriber: subscriber, input: input)
let newDemand = subscriber.receive(input)
hook?.didReceive(subscriber: subscriber,
input: input,
resultingDemand: newDemand)
return newDemand
}
internal func receive(completion: Subscribers.Completion<Subscriber.Failure>) {
let hook = DebugHook.getGlobalHook()
hook?.willReceive(subscriber: subscriber, completion: completion)
subscriber.receive(completion: completion)
hook?.didReceive(subscriber: subscriber, completion: completion)
}
internal var description: String {
return String(describing: subscriber)
}
}
@@ -8,5 +8,26 @@
internal enum SubscriptionStatus {
case awaitingSubscription
case subscribed(Subscription)
case pendingTerminal(Subscription)
case terminal
}
extension SubscriptionStatus {
internal var isAwaitingSubscription: Bool {
switch self {
case .awaitingSubscription:
return true
default:
return false
}
}
internal var subscription: Subscription? {
switch self {
case .awaitingSubscription, .terminal:
return nil
case let .subscribed(subscription), let .pendingTerminal(subscription):
return subscription
}
}
}
@@ -0,0 +1,33 @@
//
// SubscriptionTap.swift
//
//
// Created by Sergej Jaskiewicz on 27.09.2020.
//
internal struct SubscriptionTap: Subscription, CustomStringConvertible {
internal let subscription: Subscription
internal var combineIdentifier: CombineIdentifier {
return subscription.combineIdentifier
}
internal func request(_ demand: Subscribers.Demand) {
let hook = DebugHook.getGlobalHook()
hook?.willRequest(subscription: subscription, demand: demand)
subscription.request(demand)
hook?.didRequest(subscription: subscription, demand: demand)
}
internal func cancel() {
let hook = DebugHook.getGlobalHook()
hook?.willCancel(subscription: subscription)
subscription.cancel()
hook?.didCancel(subscription: subscription)
}
internal var description: String {
return String(describing: subscription)
}
}
+25 -2
View File
@@ -7,8 +7,9 @@
/// A scheduler for performing synchronous actions.
///
/// You can use this scheduler for immediate actions. If you attempt to schedule
/// actions after a specific date, the scheduler produces a fatal error.
/// You can only use this scheduler for immediate actions. If you attempt to schedule
/// actions after a specific date, this scheduler ignores the date and performs them
/// immediately.
public struct ImmediateScheduler: Scheduler {
/// The time type used by the immediate scheduler.
@@ -40,29 +41,42 @@ public struct ImmediateScheduler: Scheduler {
Codable,
SchedulerTimeIntervalConvertible {
/// The type used when evaluating floating-point literals.
public typealias FloatLiteralType = Double
/// The type used when evaluating integer literals.
public typealias IntegerLiteralType = Int
/// The type used for expressing the strides magnitude.
public typealias Magnitude = Int
/// The value of this time interval in seconds.
public var magnitude: Int
/// Creates an immediate scheduler time interval from the given time interval.
@inlinable
public init(_ value: Int) {
magnitude = value
}
/// Creates an immediate scheduler time interval from an integer seconds
/// value.
@inlinable
public init(integerLiteral value: Int) {
self.init(value)
}
/// Creates an immediate scheduler time interval from a floating-point seconds
/// value.
@inlinable
public init(floatLiteral value: Double) {
self.init(Int(value))
}
/// Creates an immediate scheduler time interval from a binary integer type.
///
/// If `exactly` cant convert to an `Int`, the resulting time interval is
/// `nil`.
@inlinable
public init?<BinaryIntegerType: BinaryInteger>(
exactly source: BinaryIntegerType
@@ -118,6 +132,7 @@ public struct ImmediateScheduler: Scheduler {
}
}
/// A type that defines options accepted by the immediate scheduler.
public typealias SchedulerOptions = Never
/// The shared instance of the immediate scheduler.
@@ -126,15 +141,21 @@ public struct ImmediateScheduler: Scheduler {
/// the shared instance.
public static let shared = ImmediateScheduler()
/// Performs the action at the next possible opportunity.
@inlinable
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
action()
}
/// The immediate schedulers definition of the current moment in time.
public var now: SchedulerTimeType { return SchedulerTimeType() }
/// The minimum tolerance allowed by the immediate scheduler.
public var minimumTolerance: SchedulerTimeType.Stride { return 0 }
/// Performs the action at some time after the specified date.
///
/// The immediate scheduler ignores `date` and performs the action immediately.
public func schedule(after date: SchedulerTimeType,
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
@@ -144,6 +165,8 @@ public struct ImmediateScheduler: Scheduler {
/// Performs the action at some time after the specified date, at the specified
/// frequency, optionally taking into account tolerance if possible.
///
/// The immediate scheduler ignores `date` and performs the action immediately.
public func schedule(after date: SchedulerTimeType,
interval: SchedulerTimeType.Stride,
tolerance: SchedulerTimeType.Stride,
+227
View File
@@ -0,0 +1,227 @@
//
// ObservableObject.swift
//
//
// Created by Sergej Jaskiewicz on 08/09/2019.
//
/// A type of object with a publisher that emits before the object has changed.
///
/// By default an `ObservableObject` synthesizes an `objectWillChange` publisher that
/// emits the changed value before any of its `@Published` properties changes.
///
/// class Contact : ObservableObject {
/// @Published var name: String
/// @Published var age: Int
///
/// init(name: String, age: Int) {
/// self.name = name
/// self.age = age
/// }
///
/// func haveBirthday() -> Int {
/// age += 1
/// }
/// }
///
/// let john = Contact(name: "John Appleseed", age: 24)
/// cancellable = john.objectWillChange
/// .sink { _ in
/// print("\(john.age) will change")
/// }
/// print(john.haveBirthday())
/// // Prints "24 will change"
/// // Prints "25"
public protocol ObservableObject: AnyObject {
/// The type of publisher that emits before the object has changed.
associatedtype ObjectWillChangePublisher: Publisher = ObservableObjectPublisher
where ObjectWillChangePublisher.Failure == Never
/// A publisher that emits before the object has changed.
var objectWillChange: ObjectWillChangePublisher { get }
}
private protocol _ObservableObjectProperty {
var objectWillChange: ObservableObjectPublisher? { get nonmutating set }
}
#if swift(>=5.1)
extension Published: _ObservableObjectProperty {}
extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
/// A publisher that emits before the object has changed.
public var objectWillChange: ObservableObjectPublisher {
var installedPublisher: ObservableObjectPublisher?
var reflection: Mirror? = Mirror(reflecting: self)
while let aClass = reflection {
for (_, property) in aClass.children {
guard let property = property as? _ObservableObjectProperty else {
// Visit other fields until we meet a @Published field
continue
}
// Now we know that the field is @Published.
if let alreadyInstalledPublisher = property.objectWillChange {
installedPublisher = alreadyInstalledPublisher
// Don't visit other fields, as all @Published fields
// already have a publisher installed.
break
}
// Okay, this field doesn't have a publisher installed.
// This means that other fields don't have it either
// (because we install it only once and fields can't be added at runtime).
var lazilyCreatedPublisher: ObjectWillChangePublisher {
if let publisher = installedPublisher {
return publisher
}
let publisher = ObservableObjectPublisher()
installedPublisher = publisher
return publisher
}
property.objectWillChange = lazilyCreatedPublisher
// Continue visiting other fields.
}
reflection = aClass.superclassMirror
}
return installedPublisher ?? ObservableObjectPublisher()
}
}
#endif
/// A publisher that publishes changes from observable objects.
public final class ObservableObjectPublisher: Publisher {
public typealias Output = Void
public typealias Failure = Never
private let lock = UnfairLock.allocate()
private var connections = Set<Conduit>()
// TODO: Combine needs this for some reason
private var identifier: ObjectIdentifier?
/// Creates an observable object publisher instance.
public init() {}
deinit {
lock.deallocate()
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Void, Downstream.Failure == Never
{
let inner = Inner(downstream: subscriber, parent: self)
lock.lock()
connections.insert(inner)
lock.unlock()
subscriber.receive(subscription: inner)
}
public func send() {
lock.lock()
let connections = self.connections
lock.unlock()
for connection in connections {
connection.send()
}
}
private func remove(_ conduit: Conduit) {
lock.lock()
connections.remove(conduit)
lock.unlock()
}
}
extension ObservableObjectPublisher {
private class Conduit: Hashable {
fileprivate func send() {
abstractMethod()
}
fileprivate static func == (lhs: Conduit, rhs: Conduit) -> Bool {
return lhs === rhs
}
fileprivate func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
private final class Inner<Downstream: Subscriber>
: Conduit,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Void, Downstream.Failure == Never
{
private enum State {
case initialized
case active
case terminal
}
private weak var parent: ObservableObjectPublisher?
private let downstream: Downstream
private let downstreamLock = UnfairRecursiveLock.allocate()
private let lock = UnfairLock.allocate()
private var state = State.initialized
init(downstream: Downstream, parent: ObservableObjectPublisher) {
self.parent = parent
self.downstream = downstream
}
deinit {
downstreamLock.deallocate()
lock.deallocate()
}
override func send() {
lock.lock()
let state = self.state
lock.unlock()
if state == .active {
downstreamLock.lock()
_ = downstream.receive()
downstreamLock.unlock()
}
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
if state == .initialized {
state = .active
}
lock.unlock()
}
func cancel() {
lock.lock()
state = .terminal
lock.unlock()
parent?.remove(self)
}
var description: String { return "ObservableObjectPublisher" }
var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
return Mirror(self, children: children)
}
var playgroundDescription: Any {
return description
}
}
}
+164 -75
View File
@@ -5,20 +5,24 @@
// Created by Sergej Jaskiewicz on 11.06.2019.
//
import COpenCombineHelpers
/// A subject that passes along values and completion.
/// A subject that broadcasts elements to downstream subscribers.
///
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
/// specific values on-demand during tests.
public final class PassthroughSubject<Output, Failure: Error>: Subject {
/// As a concrete implementation of `Subject`, the `PassthroughSubject` provides
/// a convenient way to adapt existing imperative code to the Combine model.
///
/// Unlike `CurrentValueSubject`, a `PassthroughSubject` doesnt have an initial value or
/// a buffer of the most recently-published element.
/// A `PassthroughSubject` drops values if there are no subscribers, or its current demand
/// is zero.
public final class PassthroughSubject<Output, Failure: Error>: Subject {
private let _lock = UnfairRecursiveLock.allocate()
private let lock = UnfairLock.allocate()
private var _completion: Subscribers.Completion<Failure>?
private var active = true
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
private var completion: Subscribers.Completion<Failure>?
private var downstreams = ConduitList<Output, Failure>.empty
internal var upstreamSubscriptions: [Subscription] = []
@@ -27,112 +31,197 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
public init() {}
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
for subscription in upstreamSubscriptions {
subscription.cancel()
}
_lock.deallocate()
lock.deallocate()
}
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
if hasAnyDownstreamDemand {
subscription.request(.unlimited)
}
lock.lock()
upstreamSubscriptions.append(subscription)
let hasAnyDownstreamDemand = self.hasAnyDownstreamDemand
lock.unlock()
if hasAnyDownstreamDemand {
subscription.request(.unlimited)
}
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
lock.lock()
if active {
let conduit = Conduit(parent: self, downstream: subscriber)
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
} else {
let completion = self.completion!
lock.unlock()
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
}
}
public func send(_ input: Output) {
_lock.do {
for subscription in _subscriptions
where !subscription._isCompleted && subscription._demand > 0
{
let newDemand = subscription._downstream?.receive(input) ?? .none
subscription._demand += newDemand
subscription._demand -= 1
}
lock.lock()
guard active else {
lock.unlock()
return
}
let downstreams = self.downstreams
lock.unlock()
downstreams.forEach { conduit in
conduit.offer(input)
}
}
public func send(completion: Subscribers.Completion<Failure>) {
_lock.do {
_completion = completion
for subscriber in _subscriptions {
subscriber._receive(completion: completion)
}
lock.lock()
guard active else {
lock.unlock()
return
}
active = false
self.completion = completion
let downstreams = self.downstreams
self.downstreams.removeAll()
lock.unlock()
downstreams.forEach { conduit in
conduit.finish(completion: completion)
}
}
private func _acknowledgeDownstreamDemand() {
_lock.do {
guard !hasAnyDownstreamDemand else { return }
hasAnyDownstreamDemand = true
for subscription in upstreamSubscriptions {
subscription.request(.unlimited)
}
private func acknowledgeDownstreamDemand() {
lock.lock()
if hasAnyDownstreamDemand {
lock.unlock()
return
}
hasAnyDownstreamDemand = true
let upstreamSubscriptions = self.upstreamSubscriptions
lock.unlock()
for subscription in upstreamSubscriptions {
subscription.request(.unlimited)
}
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
guard active else {
lock.unlock()
return
}
downstreams.remove(conduit)
lock.unlock()
}
}
extension PassthroughSubject {
fileprivate final class Conduit: Subscription {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
fileprivate var _parent: PassthroughSubject?
fileprivate var parent: PassthroughSubject?
fileprivate var _downstream: AnySubscriber<Output, Failure>?
fileprivate var downstream: Downstream?
fileprivate var _demand: Subscribers.Demand = .none
fileprivate var demand = Subscribers.Demand.none
fileprivate var _isCompleted: Bool {
return _parent == nil
}
private var lock = UnfairLock.allocate()
private var downstreamLock = UnfairRecursiveLock.allocate()
fileprivate init(parent: PassthroughSubject,
downstream: AnySubscriber<Output, Failure>) {
_parent = parent
_downstream = downstream
downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !_isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
override func offer(_ output: Output) {
lock.lock()
guard demand > 0, let downstream = self.downstream else {
lock.unlock()
return
}
demand -= 1
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(output)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
demand += newDemand
lock.unlock()
}
fileprivate func request(_ demand: Subscribers.Demand) {
precondition(demand > 0, "demand must not be zero")
_parent?._lock.do {
_demand += demand
override func finish(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
_parent?._acknowledgeDownstreamDemand()
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
fileprivate func cancel() {
_parent = nil
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.demand += demand
let parent = self.parent
lock.unlock()
parent?.acknowledgeDownstreamDemand()
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "PassthroughSubject" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("demand", demand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension PassthroughSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "PassthroughSubject" }
}
+180 -54
View File
@@ -6,95 +6,221 @@
//
#if swift(>=5.1)
/// Adds a `Publisher` to a property.
extension Publisher where Failure == Never {
/// Republishes elements received from a publisher, by assigning them to a property
/// marked as a publisher.
///
/// Use this operator when you want to receive elements from a publisher and republish
/// them through a property marked with the `@Published` attribute. The `assign(to:)`
/// operator manages the life cycle of the subscription, canceling the subscription
/// automatically when the `Published` instance deinitializes. Because of this,
/// the `assign(to:)` operator doesn't return an `AnyCancellable` that you're
/// responsible for like `assign(to:on:)` does.
///
/// The example below shows a model class that receives elements from an internal
/// `Timer.TimerPublisher`, and assigns them to a `@Published` property called
/// `lastUpdated`:
///
/// class MyModel: ObservableObject {
/// @Published var lastUpdated: Date = Date()
/// init() {
/// Timer.publish(every: 1.0, on: .main, in: .common)
/// .autoconnect()
/// .assign(to: $lastUpdated)
/// }
/// }
///
/// If you instead implemented `MyModel` with `assign(to: lastUpdated, on: self)`,
/// storing the returned `AnyCancellable` instance could cause a reference cycle,
/// because the `Subscribers.Assign` subscriber would hold a strong reference
/// to `self`. Using `assign(to:)` solves this problem.
///
/// - Parameter published: A property marked with the `@Published` attribute, which
/// receives and republishes all elements received from the upstream publisher.
public func assign(to published: inout Published<Output>.Publisher) {
subscribe(PublishedSubscriber(published.subject))
}
}
/// A type that publishes a property marked with an attribute.
///
/// Properties annotated with `@Published` contain both the stored value
/// and a publisher which sends any new values after the property value
/// has been sent. New subscribers will receive the current value
/// of the property first.
/// 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
}
/// Publishing a property with the `@Published` attribute creates a publisher of this
/// type. You access the publisher with the `$` operator, as shown here:
///
/// class Weather {
/// @Published var temperature: Double
/// init(temperature: Double) {
/// self.temperature = temperature
/// }
/// }
///
/// let weather = Weather(temperature: 20)
/// cancellable = weather.$temperature
/// .sink() {
/// print ("Temperature now: \($0)")
/// }
/// weather.temperature = 25
///
/// // Prints:
/// // Temperature now: 20.0
/// // Temperature now: 25.0
///
/// When the property changes, publishing occurs in the property's `willSet` block,
/// meaning subscribers receive the new value before it's actually set on the property.
/// In the above example, the second time the sink executes its closure, it receives
/// the parameter value `25`. However, if the closure evaluated `weather.temperature`,
/// the value returned would be `20`.
///
/// > Important: The `@Published` attribute is class constrained. Use it with properties
/// of classes, not with non-class types like structures.
///
/// ### See Also
///
/// - `Publisher.assign(to:)`
@available(swift, introduced: 5.1)
@propertyWrapper
public struct Published<Value> {
/// A publisher for properties marked with the `@Published` attribute.
public struct Publisher: OpenCombine.Publisher {
/// The kind of values published by this publisher.
public typealias Output = Value
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
/// This function is called to attach the specified
/// `Subscriber` to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
fileprivate let subject: PublishedSubject<Value>
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Value, Downstream.Failure == Never
{
subject.subscribe(subscriber)
}
fileprivate let subject: OpenCombine.CurrentValueSubject<Value, Never>
fileprivate init(_ output: Output) {
subject = .init(output)
}
}
private var value: Value
private enum Storage {
case value(Value)
case publisher(Publisher)
}
@propertyWrapper
private final class Box {
var wrappedValue: Storage
/// The property that can be accessed with the
/// `$` syntax and allows access to the `Publisher`
init(wrappedValue: Storage) {
self.wrappedValue = wrappedValue
}
}
@Box private var storage: Storage
internal var objectWillChange: ObservableObjectPublisher? {
get {
switch storage {
case .value:
return nil
case .publisher(let publisher):
return publisher.subject.objectWillChange
}
}
nonmutating set {
getPublisher().subject.objectWillChange = newValue
}
}
/// Creates the published instance with an initial wrapped value.
///
/// Don't use this initializer directly. Instead, create a property with
/// the `@Published` attribute, as shown here:
///
/// @Published var lastUpdated: Date = Date()
///
/// - Parameter wrappedValue: The publisher's initial value.
public init(initialValue: Value) {
self.init(wrappedValue: initialValue)
}
/// Creates the published instance with an initial value.
///
/// Don't use this initializer directly. Instead, create a property with
/// the `@Published` attribute, as shown here:
///
/// @Published var lastUpdated: Date = Date()
///
/// - Parameter initialValue: The publisher's initial value.
public init(wrappedValue: Value) {
_storage = Box(wrappedValue: .value(wrappedValue))
}
/// The property for which this instance exposes a publisher.
///
/// The `projectedValue` is the property accessed with the `$` operator.
public var projectedValue: Publisher {
mutating get {
if let publisher = publisher {
return publisher
return getPublisher()
}
set { // swiftlint:disable:this unused_setter_value
switch storage {
case .value(let value):
storage = .publisher(Publisher(value))
case .publisher:
break
}
}
}
/// Note: This method can mutate `storage`
internal func getPublisher() -> Publisher {
switch storage {
case .value(let value):
let publisher = Publisher(value)
self.publisher = publisher
storage = .publisher(publisher)
return publisher
case .publisher(let publisher):
return publisher
}
}
@available(*, unavailable, message:
"@Published is only available on properties of classes")
// swiftlint:disable let_var_whitespace
@available(*, unavailable, message: """
@Published is only available on properties of classes
""")
public var wrappedValue: Value {
get { value }
set {
value = newValue
publisher?.subject.value = newValue
}
get { fatalError() }
set { fatalError() } // swiftlint:disable:this unused_setter_value
}
// swiftlint:enable let_var_whitespace
private var publisher: Publisher?
@available(*, unavailable, message:
"This subscript is unavailable in OpenCombine yet")
public static subscript<EnclosingSelf: AnyObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
) -> Value {
get { fatalError() }
set { fatalError() }
get {
switch object[keyPath: storageKeyPath].storage {
case .value(let value):
return value
case .publisher(let publisher):
return publisher.subject.value
}
}
set {
switch object[keyPath: storageKeyPath].storage {
case .value:
object[keyPath: storageKeyPath].storage = .publisher(Publisher(newValue))
case .publisher(let publisher):
publisher.subject.value = newValue
}
}
// TODO: Benchmark and explore a possibility to use _modify
}
}
#endif
#else
@available(swift, introduced: 5.1)
public typealias Published = Never
#endif // swift(>=5.1)
+58 -16
View File
@@ -7,18 +7,40 @@
/// Declares that a type can transmit a sequence of values over time.
///
/// There are four kinds of messages:
/// subscription - A connection between `Publisher` and `Subscriber`.
/// value - An element in the sequence.
/// error - The sequence ended with an error (`.failure(e)`).
/// complete - The sequence ended successfully (`.finished`).
/// A publisher delivers elements to one or more `Subscriber` instances.
/// The subscribers `Input` and `Failure` associated types must match the `Output` and
/// `Failure` types declared by the publisher.
/// The publisher implements the `receive(subscriber:)`method to accept a subscriber.
///
/// Both `.failure` and `.finished` are terminal messages.
/// After this, the publisher can call the following methods on the subscriber:
/// - `receive(subscription:)`: Acknowledges the subscribe request and returns
/// a `Subscription` instance. The subscriber uses the subscription to demand elements
/// from the publisher and can use it to cancel publishing.
/// - `receive(_:)`: Delivers one element from the publisher to the subscriber.
/// - `receive(completion:)`: Informs the subscriber that publishing has ended,
/// either normally or with an error.
///
/// You can summarize these possibilities with a regular expression:
/// value*(error|finished)?
/// Every `Publisher` must adhere to this contract for downstream subscribers to function
/// correctly.
///
/// Every `Publisher` must adhere to this contract.
/// Extensions on `Publisher` define a wide variety of _operators_ that you compose to
/// create sophisticated event-processing chains.
/// Each operator returns a type that implements the `Publisher` protocol
/// Most of these types exist as extensions on the `Publishers` enumeration.
/// For example, the `map(_:)` operator returns an instance of `Publishers.Map`.
///
/// # Creating Your Own Publishers
///
/// Rather than implementing the `Publisher` protocol yourself, you can create your own
/// publisher by using one of several types provided by the OpenCombine framework:
///
/// - Use a concrete subclass of `Subject`, such as `PassthroughSubject`, to publish
/// values on-demand by calling its `send(_:)` method.
/// - Use a `CurrentValueSubject` to publish whenever you update the subjects underlying
/// value.
/// - Add the `@Published` annotation to a property of one of your own types. In doing so,
/// the property gains a publisher that emits an event whenever the propertys value
/// changes. See the `Published` type for an example of this approach.
public protocol Publisher {
/// The kind of values published by this publisher.
@@ -29,13 +51,15 @@ public protocol Publisher {
/// Use `Never` if this `Publisher` does not publish errors.
associatedtype Failure: Error
/// This function is called to attach the specified `Subscriber` to this `Publisher`
/// by `subscribe(_:)`
/// Attaches the specified subscriber to this publisher.
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
/// Always call this function instead of `receive(subscriber:)`.
/// Adopters of `Publisher` must implement `receive(subscriber:)`. The implementation
/// of `subscribe(_:)` provided by `Publisher` calls through to
/// `receive(subscriber:)`.
///
/// - Parameter subscriber: The subscriber to attach to this publisher. After
/// attaching, the subscriber can start to receive values.
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Failure == Subscriber.Failure, Output == Subscriber.Input
}
@@ -55,9 +79,27 @@ extension Publisher {
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
where Failure == Subscriber.Failure, Output == Subscriber.Input
{
receive(subscriber: subscriber)
if let hook = DebugHook.getGlobalHook() {
if var marker = subscriber as? SubscriberTapMarker {
let anySubscriber = marker.inner
as! AnySubscriber<Subscriber.Input, Subscriber.Failure>
hook.willReceive(publisher: self, subscriber: anySubscriber)
receive(subscriber: subscriber)
hook.didReceive(publisher: self, subscriber: anySubscriber)
} else {
let tap = SubscriberTap(subscriber: subscriber)
hook.willReceive(publisher: self, subscriber: subscriber)
receive(subscriber: tap)
hook.didReceive(publisher: self, subscriber: subscriber)
}
} else {
receive(subscriber: subscriber)
}
}
/// Attaches the specified subject to this publisher.
///
/// - Parameter subject: The subject to attach to this publisher.
public func subscribe<Subject: OpenCombine.Subject>(
_ subject: Subject
) -> AnyCancellable
@@ -17,7 +17,7 @@ public struct Deferred<DeferredPublisher: Publisher>: Publisher {
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = DeferredPublisher.Failure
/// The closure to execute when it receives a subscription.
/// The closure to execute when this deferred publisher receives a subscription.
///
/// The publisher returned by this closure immediately
/// receives the incoming subscription.
@@ -24,6 +24,7 @@ public struct Empty<Output, Failure: Error>: Publisher, Equatable {
///
/// Use this initializer to connect the empty publisher to subscribers or other
/// publishers that have specific output and failure types.
///
/// - Parameters:
/// - completeImmediately: A Boolean value that indicates whether the publisher
/// should immediately finish.
@@ -20,6 +20,7 @@ public struct Fail<Output, Failure: Error>: Publisher {
///
/// Use this initializer to create a `Fail` publisher that can work with
/// subscribers or publishers that expect a given output type.
///
/// - Parameters:
/// - outputType: The output type exposed by this publisher.
/// - failure: The failure to send when terminating the publisher.
@@ -0,0 +1,673 @@
//
//
// Auto-generated from GYB template. DO NOT EDIT!
//
//
//
//
// Publishers.Catch.swift
//
//
// Created by Sergej Jaskiewicz on 25.12.2019.
//
extension Publisher {
/// Handles errors from an upstream publisher by replacing it with another publisher.
///
/// Use `catch()` to replace an error from an upstream publisher with a new publisher.
///
/// In the example below, the `catch()` operator handles the `SimpleError` thrown by
/// the upstream publisher by replacing the error with a `Just` publisher. This
/// continues the stream by publishing a single value and completing normally.
///
/// struct SimpleError: Error {}
/// let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
/// cancellable = numbers.publisher
/// .tryLast(where: {
/// guard $0 != 0 else { throw SimpleError() }
/// return true
/// })
/// .catch { error in
/// Just(-1)
/// }
/// .sink { print("\($0)") }
/// // Prints: -1
///
/// Backpressure note: This publisher passes through `request` and `cancel` to
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
/// demand to the new `Publisher`.
///
/// - SeeAlso: `replaceError`
/// - Parameter handler: A closure that accepts the upstream failure as input and
/// returns a publisher to replace the upstream publisher.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher.
public func `catch`<NewPublisher: Publisher>(
_ handler: @escaping (Failure) -> NewPublisher
) -> Publishers.Catch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
/// Handles errors from an upstream publisher by either replacing it with another
/// publisher or throwing a new error.
///
/// Use `tryCatch(_:)` to decide how to handle from an upstream publisher by either
/// replacing the publisher with a new publisher, or throwing a new error.
///
/// In the example below, an array publisher emits values that a `tryMap(_:)` operator
/// evaluates to ensure the values are greater than zero. If the values arent greater
/// than zero, the operator throws an error to the downstream subscriber to let it
/// know there was a problem. The subscriber, `tryCatch(_:)`, replaces the error with
/// a new publisher using ``Just`` to publish a final value before the stream ends
/// normally.
///
/// enum SimpleError: Error { case error }
/// var numbers = [5, 4, 3, 2, 1, -1, 7, 8, 9, 10]
///
/// cancellable = numbers.publisher
/// .tryMap { v in
/// if v > 0 {
/// return v
/// } else {
/// throw SimpleError.error
/// }
/// }
/// .tryCatch { error in
/// Just(0) // Send a final value before completing normally.
/// // Alternatively, throw a new error to terminate the stream.
/// }
/// .sink(receiveCompletion: { print ("Completion: \($0).") },
/// receiveValue: { print ("Received \($0).") })
/// // Received 5.
/// // Received 4.
/// // Received 3.
/// // Received 2.
/// // Received 1.
/// // Received 0.
/// // Completion: finished.
///
/// - Parameter handler: A throwing closure that accepts the upstream failure as
/// input. This closure can either replace the upstream publisher with a new one,
/// or throw a new error to the downstream subscriber.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher, or an error.
public func tryCatch<NewPublisher: Publisher>(
_ handler: @escaping (Failure) throws -> NewPublisher
) -> Publishers.TryCatch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
}
extension Publishers {
/// A publisher that handles errors from an upstream publisher by replacing the failed
/// publisher with another publisher.
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = NewPublisher.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that accepts the upstream failure as input and returns a publisher
/// to replace the upstream publisher.
public let handler: (Upstream.Failure) -> NewPublisher
/// Creates a publisher that handles errors from an upstream publisher by
/// replacing the failed publisher with another publisher.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - handler: A closure that accepts the upstream failure as input and returns
/// a publisher to replace the upstream publisher.
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
/// A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher or producing a new error.
///
/// Because this publishers handler can throw an error, `Publishers.TryCatch` defines
/// its `Failure` type as `Error`. This is different from `Publishers.Catch`, which
/// gets its failure type from the replacement publisher.
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that accepts the upstream failure as input and either returns
/// a publisher to replace the upstream publisher or throws an error.
public let handler: (Upstream.Failure) throws -> NewPublisher
/// Creates a publisher that handles errors from an upstream publisher by
/// replacing the failed publisher with another publisher or by throwing an error.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - handler: A closure that accepts the upstream failure as input and either
/// returns a publisher to replace the upstream publisher. If this closure
/// throws an error, the publisher terminates with the thrown error.
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
}
extension Publishers.Catch {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output,
Downstream.Failure == NewPublisher.Failure
{
struct UncaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePre(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePre(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
return inner.receivePre(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
struct CaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = NewPublisher.Output
typealias Failure = NewPublisher.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePost(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePost(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
inner.receivePost(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
private enum State {
case pendingPre
case pre(Subscription)
case pendingPost
case post(Subscription)
case cancelled
}
private let lock = UnfairLock.allocate()
private var demand = Subscribers.Demand.none
private var state = State.pendingPre
private let downstream: Downstream
private let handler: (Upstream.Failure) -> NewPublisher
init(downstream: Downstream,
handler: @escaping (Upstream.Failure) -> NewPublisher) {
self.downstream = downstream
self.handler = handler
}
deinit {
lock.deallocate()
}
func receivePre(subscription: Subscription) {
lock.lock()
guard case .pendingPre = state else {
lock.unlock()
subscription.cancel()
return
}
state = .pre(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
let newDemand = downstream.receive(input)
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
lock.lock()
switch state {
case .pre:
state = .cancelled
lock.unlock()
downstream.receive(completion: .finished)
case .pendingPre, .pendingPost, .post, .cancelled:
lock.unlock()
}
case .failure(let error):
lock.lock()
switch state {
case .pre:
state = .pendingPost
lock.unlock()
handler(error).subscribe(CaughtS(inner: self))
case .cancelled:
lock.unlock()
case .pendingPre, .post, .pendingPost:
completionBeforeSubscription()
}
}
}
func receivePost(subscription: Subscription) {
lock.lock()
guard case .pendingPost = state else {
lock.unlock()
subscription.cancel()
return
}
state = .post(subscription)
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
lock.lock()
guard case .post = state else {
lock.unlock()
return
}
state = .cancelled
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .pendingPre:
// The client is only able to call the `request` method after we've sent
// `self` downstream. We only do it in the `receivePre(subscription:)`
// method, after setting `state` to `pre`.
// After that `state` never becomes `pendingPre`.
requestBeforeSubscription()
case let .pre(subscription):
self.demand += demand
lock.unlock()
subscription.request(demand)
case .pendingPost:
self.demand += demand
lock.unlock()
case let .post(subscription):
lock.unlock()
subscription.request(demand)
case .cancelled:
lock.unlock()
}
}
func cancel() {
lock.lock()
switch state {
case let .pre(subscription), let .post(subscription):
state = .cancelled
lock.unlock()
subscription.cancel()
case .pendingPre, .pendingPost, .cancelled:
lock.unlock()
}
}
var description: String { return "Catch" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryCatch {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output,
Downstream.Failure == Error
{
struct UncaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePre(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePre(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
return inner.receivePre(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
struct CaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = NewPublisher.Output
typealias Failure = NewPublisher.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePost(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePost(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
inner.receivePost(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
private enum State {
case pendingPre
case pre(Subscription)
case pendingPost
case post(Subscription)
case cancelled
}
private let lock = UnfairLock.allocate()
private var demand = Subscribers.Demand.none
private var state = State.pendingPre
private let downstream: Downstream
private let handler: (Upstream.Failure) throws -> NewPublisher
init(downstream: Downstream,
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
self.downstream = downstream
self.handler = handler
}
deinit {
lock.deallocate()
}
func receivePre(subscription: Subscription) {
lock.lock()
guard case .pendingPre = state else {
lock.unlock()
subscription.cancel()
return
}
state = .pre(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
let newDemand = downstream.receive(input)
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
lock.lock()
switch state {
case .pre:
state = .cancelled
lock.unlock()
downstream.receive(completion: .finished)
case .pendingPre, .pendingPost, .post, .cancelled:
lock.unlock()
}
case .failure(let error):
lock.lock()
switch state {
case .pre:
state = .pendingPost
lock.unlock()
do {
try handler(error).subscribe(CaughtS(inner: self))
} catch let anotherError {
lock.lock()
state = .cancelled
lock.unlock()
downstream.receive(completion: .failure(anotherError))
}
case .cancelled:
lock.unlock()
case .pendingPre, .post, .pendingPost:
completionBeforeSubscription()
}
}
}
func receivePost(subscription: Subscription) {
lock.lock()
guard case .pendingPost = state else {
lock.unlock()
subscription.cancel()
return
}
state = .post(subscription)
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
lock.lock()
guard case .post = state else {
lock.unlock()
return
}
state = .cancelled
lock.unlock()
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .pendingPre:
// The client is only able to call the `request` method after we've sent
// `self` downstream. We only do it in the `receivePre(subscription:)`
// method, after setting `state` to `pre`.
// After that `state` never becomes `pendingPre`.
requestBeforeSubscription()
case let .pre(subscription):
self.demand += demand
lock.unlock()
subscription.request(demand)
case .pendingPost:
self.demand += demand
lock.unlock()
case let .post(subscription):
lock.unlock()
subscription.request(demand)
case .cancelled:
lock.unlock()
}
}
func cancel() {
lock.lock()
switch state {
case let .pre(subscription), let .post(subscription):
state = .cancelled
lock.unlock()
subscription.cancel()
case .pendingPre, .pendingPost, .cancelled:
lock.unlock()
}
}
var description: String { return "TryCatch" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
private func completionBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: received completion but do not have subscription",
file: file,
line: line)
}
private func requestBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: request before subscription sent",
file: file,
line: line)
}
@@ -0,0 +1,408 @@
//
//
// Auto-generated from GYB template. DO NOT EDIT!
//
//
//
//
// Publishers.CombineLatest.swift.gyb
//
//
// Created by Sergej Jaskiewicz on 10.12.2019.
//
// swiftlint:disable generic_type_name
// swiftlint:disable large_tuple
// MARK: - CombineLatest methods on Publisher
extension Publisher {
/// Subscribes to an additional publisher and publishes a tuple upon
/// receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
/// - other: Another publisher to combine with this one.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
public func combineLatest<P: Publisher>(
_ other: P
) -> Publishers.CombineLatest<Self, P>
where Failure == P.Failure
{
return .init(self, other)
}
/// Subscribes to an additional publisher and invokes a closure
/// upon receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
/// - other: Another publisher to combine with this one.
/// - transform: A closure that receives the most recent value from each publisher
/// and returns a new value to publish.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
public func combineLatest<P: Publisher, Result>(
_ other: P,
_ transform: @escaping (Output, P.Output) -> Result
) -> Publishers.Map<Publishers.CombineLatest<Self, P>, Result>
where Failure == P.Failure
{
return Publishers.CombineLatest(self, other).map {
transform($0, $1)
}
}
/// Subscribes to two additional publishers and publishes a tuple upon
/// receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
/// - publisher1: A second publisher to combine with this one.
/// - publisher2: A third publisher to combine with this one.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
public func combineLatest<P: Publisher, Q: Publisher>(
_ publisher1: P,
_ publisher2: Q
) -> Publishers.CombineLatest3<Self, P, Q>
where Failure == P.Failure,
P.Failure == Q.Failure
{
return .init(self, publisher1, publisher2)
}
/// Subscribes to two additional publishers and invokes a closure
/// upon receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
/// - publisher1: A second publisher to combine with this one.
/// - publisher2: A third publisher to combine with this one.
/// - transform: A closure that receives the most recent value from each publisher
/// and returns a new value to publish.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
public func combineLatest<P: Publisher, Q: Publisher, Result>(
_ publisher1: P,
_ publisher2: Q,
_ transform: @escaping (Output, P.Output, Q.Output) -> Result
) -> Publishers.Map<Publishers.CombineLatest3<Self, P, Q>, Result>
where Failure == P.Failure,
P.Failure == Q.Failure
{
return Publishers.CombineLatest3(self, publisher1, publisher2).map {
transform($0, $1, $2)
}
}
/// Subscribes to three additional publishers and publishes a tuple upon
/// receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
/// - publisher1: A second publisher to combine with this one.
/// - publisher2: A third publisher to combine with this one.
/// - publisher3: A fourth publisher to combine with this one.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
public func combineLatest<P: Publisher, Q: Publisher, R: Publisher>(
_ publisher1: P,
_ publisher2: Q,
_ publisher3: R
) -> Publishers.CombineLatest4<Self, P, Q, R>
where Failure == P.Failure,
P.Failure == Q.Failure,
Q.Failure == R.Failure
{
return .init(self, publisher1, publisher2, publisher3)
}
/// Subscribes to three additional publishers and invokes a closure
/// upon receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
/// - publisher1: A second publisher to combine with this one.
/// - publisher2: A third publisher to combine with this one.
/// - publisher3: A fourth publisher to combine with this one.
/// - transform: A closure that receives the most recent value from each publisher
/// and returns a new value to publish.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
public func combineLatest<P: Publisher, Q: Publisher, R: Publisher, Result>(
_ publisher1: P,
_ publisher2: Q,
_ publisher3: R,
_ transform: @escaping (Output, P.Output, Q.Output, R.Output) -> Result
) -> Publishers.Map<Publishers.CombineLatest4<Self, P, Q, R>, Result>
where Failure == P.Failure,
P.Failure == Q.Failure,
Q.Failure == R.Failure
{
return Publishers.CombineLatest4(self, publisher1, publisher2, publisher3).map {
transform($0, $1, $2, $3)
}
}
}
// MARK: - CombineLatest publishers
extension Publishers {
/// A publisher that receives and combines the latest elements from two
/// publishers.
public struct CombineLatest<A: Publisher, B: Publisher>
: Publisher
where A.Failure == B.Failure
{
public typealias Output = (A.Output, B.Output)
public typealias Failure = A.Failure
public let a: A
public let b: B
public init(
_ a: A,
_ b: B
) {
self.a = a
self.b = b
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure,
Downstream.Input == Output
{
typealias Inner = CombineLatest2Inner<A.Output,
B.Output,
Failure,
Downstream>
let inner = Inner(downstream: subscriber, upstreamCount: 2)
a.subscribe(Inner.Side(index: 0, combiner: inner))
b.subscribe(Inner.Side(index: 1, combiner: inner))
subscriber.receive(subscription: inner)
}
}
/// A publisher that receives and combines the latest elements from three
/// publishers.
public struct CombineLatest3<A: Publisher, B: Publisher, C: Publisher>
: Publisher
where A.Failure == B.Failure,
B.Failure == C.Failure
{
public typealias Output = (A.Output, B.Output, C.Output)
public typealias Failure = A.Failure
public let a: A
public let b: B
public let c: C
public init(
_ a: A,
_ b: B,
_ c: C
) {
self.a = a
self.b = b
self.c = c
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure,
Downstream.Input == Output
{
typealias Inner = CombineLatest3Inner<A.Output,
B.Output,
C.Output,
Failure,
Downstream>
let inner = Inner(downstream: subscriber, upstreamCount: 3)
a.subscribe(Inner.Side(index: 0, combiner: inner))
b.subscribe(Inner.Side(index: 1, combiner: inner))
c.subscribe(Inner.Side(index: 2, combiner: inner))
subscriber.receive(subscription: inner)
}
}
/// A publisher that receives and combines the latest elements from four
/// publishers.
public struct CombineLatest4<A: Publisher, B: Publisher, C: Publisher, D: Publisher>
: Publisher
where A.Failure == B.Failure,
B.Failure == C.Failure,
C.Failure == D.Failure
{
public typealias Output = (A.Output, B.Output, C.Output, D.Output)
public typealias Failure = A.Failure
public let a: A
public let b: B
public let c: C
public let d: D
public init(
_ a: A,
_ b: B,
_ c: C,
_ d: D
) {
self.a = a
self.b = b
self.c = c
self.d = d
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure,
Downstream.Input == Output
{
typealias Inner = CombineLatest4Inner<A.Output,
B.Output,
C.Output,
D.Output,
Failure,
Downstream>
let inner = Inner(downstream: subscriber, upstreamCount: 4)
a.subscribe(Inner.Side(index: 0, combiner: inner))
b.subscribe(Inner.Side(index: 1, combiner: inner))
c.subscribe(Inner.Side(index: 2, combiner: inner))
d.subscribe(Inner.Side(index: 3, combiner: inner))
subscriber.receive(subscription: inner)
}
}
}
// MARK: - Equatable conformances
extension Publishers.CombineLatest: Equatable
where
A: Equatable,
B: Equatable {}
extension Publishers.CombineLatest3: Equatable
where
A: Equatable,
B: Equatable,
C: Equatable {}
extension Publishers.CombineLatest4: Equatable
where
A: Equatable,
B: Equatable,
C: Equatable,
D: Equatable {}
// MARK: - Inners
private final class CombineLatest2Inner<Input0,
Input1,
Failure,
Downstream: Subscriber>
: AbstractCombineLatest<(Input0, Input1), Failure, Downstream>
where Downstream.Input == (Input0, Input1),
Downstream.Failure == Failure
{
override func convert(values: [Any?]) -> (Input0, Input1) {
return (values[0] as! Input0,
values[1] as! Input1)
}
}
private final class CombineLatest3Inner<Input0,
Input1,
Input2,
Failure,
Downstream: Subscriber>
: AbstractCombineLatest<(Input0, Input1, Input2), Failure, Downstream>
where Downstream.Input == (Input0, Input1, Input2),
Downstream.Failure == Failure
{
override func convert(values: [Any?]) -> (Input0, Input1, Input2) {
return (values[0] as! Input0,
values[1] as! Input1,
values[2] as! Input2)
}
}
private final class CombineLatest4Inner<Input0,
Input1,
Input2,
Input3,
Failure,
Downstream: Subscriber>
: AbstractCombineLatest<(Input0, Input1, Input2, Input3), Failure, Downstream>
where Downstream.Input == (Input0, Input1, Input2, Input3),
Downstream.Failure == Failure
{
override func convert(values: [Any?]) -> (Input0, Input1, Input2, Input3) {
return (values[0] as! Input0,
values[1] as! Input1,
values[2] as! Input2,
values[3] as! Input3)
}
}
@@ -0,0 +1,392 @@
//
//
// Auto-generated from GYB template. DO NOT EDIT!
//
//
//
//
// Publishers.Encode.swift.gyb
//
//
// Created by Joseph Spadafora on 6/22/19.
//
extension Publisher {
/// Encodes the output from upstream using a specified encoder.
///
/// Use `encode(encoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
/// property lists) to encode an `Encodable` struct into `Data` that could be used to
/// make a JSON string (or written to disk as a binary plist in the case of property
/// lists).
///
/// In this example, a `PassthroughSubject` publishes an `Article`.
/// The `encode(encoder:)` operator encodes the properties of the `Article` struct
/// into a new JSON string according to the `Codable` protocol adopted by `Article`.
/// The operator publishes the resulting JSON string to the downstream subscriber.
/// If the encoding operation fails, which can happen in the case of complex
/// properties that cant be directly transformed into JSON, the stream terminates
/// and the error is passed to the downstream subscriber.
///
/// struct Article: Codable {
/// let title: String
/// let author: String
/// let pubDate: Date
/// }
///
/// let dataProvider = PassthroughSubject<Article, Never>()
/// let cancellable = dataProvider
/// .encode(encoder: JSONEncoder())
/// .sink(receiveCompletion: { print ("Completion: \($0)") },
/// receiveValue: { data in
/// guard let stringRepresentation =
/// String(data: data, encoding: .utf8) else { return }
/// print("""
/// Data received \(data) string representation: \
/// \(stringRepresentation)
/// """)
/// })
///
/// dataProvider.send(Article(title: "My First Article",
/// author: "Gita Kumar",
/// pubDate: Date()))
///
/// // Prints: "Data received 86 bytes string representation:
/// // {"title":"My First Article","author":"Gita Kumar"
/// // "pubDate":606211803.279603}"
///
/// - Parameter encoder: An encoder that implements the `TopLevelEncoder` protocol.
/// - Returns: A publisher that encodes received elements using a specified encoder,
/// and publishes the resulting data.
public func encode<Coder: TopLevelEncoder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder> {
return .init(upstream: self, encoder: encoder)
}
/// Decodes the output from the upstream using a specified decoder.
///
/// Use `decode(type:decoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
/// property lists) to decode data received from a `URLSession.DataTaskPublisher` or
/// other data source using the `Decodable` protocol.
///
/// In this example, a `PassthroughSubject` publishes a JSON string. The JSON decoder
/// parses the string, converting its fields according to the `Decodable` protocol
/// implemented by `Article`, and successfully populating a new `Article`.
/// The `Publishers.Decode` publisher then publishes the `Article` to the downstream.
/// If a decoding operation fails, which happens in the case of missing or malformed
/// data in the source JSON string, the stream terminates and passes the error to
/// the downstream subscriber.
///
/// struct Article: Codable {
/// let title: String
/// let author: String
/// let pubDate: Date
/// }
///
/// let dataProvider = PassthroughSubject<Data, Never>()
/// cancellable = dataProvider
/// .decode(type: Article.self, decoder: JSONDecoder())
/// .sink(receiveCompletion: { print ("Completion: \($0)")},
/// receiveValue: { print ("value: \($0)") })
///
/// dataProvider.send(Data("""
/// {\"pubDate\":1574273638.575666, \
/// \"title\" : \"My First Article\", \
/// \"author\" : \"Gita Kumar\" }
/// """.utf8))
///
/// // Prints:
/// // ".sink() data received Article(title: "My First Article",
/// // author: "Gita Kumar",
/// // pubDate: 2050-11-20 18:13:58 +0000)"
///
/// - Parameters:
/// - type: The encoded data to decode into a struct that conforms to
/// the `Decodable` protocol.
/// - decoder: A decoder that implements the `TopLevelDecoder` protocol.
/// - Returns: A publisher that decodes a given type using a specified decoder and
/// publishes the result.
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
type: Item.Type,
decoder: Coder
) -> Publishers.Decode<Self, Item, Coder> where Output == Coder.Input {
return .init(upstream: self, decoder: decoder)
}
}
extension Publishers {
public struct Encode<Upstream: Publisher, Coder: TopLevelEncoder>: Publisher
where Upstream.Output: Encodable
{
public typealias Failure = Error
public typealias Output = Coder.Output
public let upstream: Upstream
private let _encode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self._encode = encoder.encode
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, encode: _encode))
}
}
public struct Decode<Upstream: Publisher, Output: Decodable, Coder: TopLevelDecoder>
: Publisher
where Upstream.Output == Coder.Input
{
public typealias Failure = Error
public let upstream: Upstream
private let _decode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decode = { try decoder.decode(Output.self, from: $0) }
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, decode: _decode))
}
}
}
extension Publishers.Encode {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let encode: (Upstream.Output) throws -> Output
private let lock = UnfairLock.allocate()
private var finished = false
private var subscription: Subscription?
fileprivate init(
downstream: Downstream,
encode: @escaping (Upstream.Output) throws -> Output
) {
self.downstream = downstream
self.encode = encode
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
if finished || self.subscription != nil {
lock.unlock()
subscription.cancel()
return
}
self.subscription = subscription
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
if finished {
lock.unlock()
return .none
}
lock.unlock()
do {
return try downstream.receive(encode(input))
} catch {
lock.lock()
finished = true
let subscription = self.subscription
self.subscription = nil
lock.unlock()
subscription?.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
if finished {
lock.unlock()
return
}
finished = true
subscription = nil
lock.unlock()
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
let subscription = self.subscription
lock.unlock()
subscription?.request(demand)
}
func cancel() {
lock.lock()
guard !finished, let subscription = self.subscription else {
lock.unlock()
return
}
self.subscription = nil
finished = true
lock.unlock()
subscription.cancel()
}
var description: String { return "Encode" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("finished", finished),
("upstreamSubscription", subscription as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.Decode {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let decode: (Upstream.Output) throws -> Output
private let lock = UnfairLock.allocate()
private var finished = false
private var subscription: Subscription?
fileprivate init(
downstream: Downstream,
decode: @escaping (Upstream.Output) throws -> Output
) {
self.downstream = downstream
self.decode = decode
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
if finished || self.subscription != nil {
lock.unlock()
subscription.cancel()
return
}
self.subscription = subscription
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
if finished {
lock.unlock()
return .none
}
lock.unlock()
do {
return try downstream.receive(decode(input))
} catch {
lock.lock()
finished = true
let subscription = self.subscription
self.subscription = nil
lock.unlock()
subscription?.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
if finished {
lock.unlock()
return
}
finished = true
subscription = nil
lock.unlock()
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
let subscription = self.subscription
lock.unlock()
subscription?.request(demand)
}
func cancel() {
lock.lock()
guard !finished, let subscription = self.subscription else {
lock.unlock()
return
}
self.subscription = nil
finished = true
lock.unlock()
subscription.cancel()
}
var description: String { return "Decode" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("finished", finished),
("upstreamSubscription", subscription as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -12,11 +12,29 @@
//
extension Publisher {
/// Returns a publisher that publishes the values of a keyt path as a tuple.
/// Publishes the value of the key path.
///
/// In the following example, the `map(_:)` operator uses the Swift
/// key path syntax to access the `die` member
/// of the `DiceRoll` structure published by the `Just` publisher.
///
/// The downstream sink subscriber receives only
/// the value of this `Int`,
/// not the entire `DiceRoll`.
///
/// struct DiceRoll {
/// let die: Int
/// }
///
/// cancellable = Just(DiceRoll(die: Int.random(in: 1...6)))
/// .map(\.die)
/// .sink {
/// print ("Rolled: \($0)")
/// }
/// // Prints "Rolled: 6 (or some other random value).
///
/// - Parameters:
/// - keyPath: The key path of a property on `Output`
/// - keyPath: The key path of a property on `Output`.
/// - Returns: A publisher that publishes the value of the key path.
public func map<Result>(
_ keyPath: KeyPath<Output, Result>
@@ -26,12 +44,35 @@ extension Publisher {
keyPath: keyPath
)
}
/// Returns a publisher that publishes the values of two key paths as a tuple.
/// Publishes the values of two key paths as a tuple.
///
/// In the following example, the `map(_:_:)` operator uses the Swift
/// key path syntax to access the `die1` and `die2` members
/// of the `DiceRoll` structure published by the `Just` publisher.
///
/// The downstream sink subscriber receives only
/// these two values (as an `(Int, Int)` tuple),
/// not the entire `DiceRoll`.
///
/// struct DiceRoll {
/// let die1: Int
/// let die2: Int
/// }
///
/// cancellable = Just(DiceRoll(die1: Int.random(in: 1...6),
/// die2: Int.random(in: 1...6)))
/// .map(\.die1, \.die2)
/// .sink { values in
/// print("""
/// Rolled: \(values.0), \(values.1) \
/// (total \(values.0 + values.1))
/// """)
/// }
/// // Prints "Rolled: 5, 3 (total: 8)" (or other random values).
///
/// - Parameters:
/// - keyPath0: The key path of a property on `Output`
/// - keyPath1: The key path of another property on `Output`
/// - keyPath0: The key path of a property on `Output`.
/// - keyPath1: The key path of another property on `Output`.
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
public func map<Result0, Result1>(
_ keyPath0: KeyPath<Output, Result0>,
@@ -43,13 +84,38 @@ extension Publisher {
keyPath1: keyPath1
)
}
/// Returns a publisher that publishes the values of three key paths as a tuple.
/// Publishes the values of three key paths as a tuple.
///
/// In the following example, the `map(_:_:_:)` operator uses the Swift
/// key path syntax to access the `die1`, `die2`, and `die3` members
/// of the `DiceRoll` structure published by the `Just` publisher.
///
/// The downstream sink subscriber receives only
/// these three values (as an `(Int, Int, Int)` tuple),
/// not the entire `DiceRoll`.
///
/// struct DiceRoll {
/// let die1: Int
/// let die2: Int
/// let die3: Int
/// }
///
/// cancellable = Just(DiceRoll(die1: Int.random(in: 1...6),
/// die2: Int.random(in: 1...6),
/// die3: Int.random(in: 1...6)))
/// .map(\.die1, \.die2, \.die3)
/// .sink { values in
/// print("""
/// Rolled: \(values.0), \(values.1), \(values.2) \
/// (total \(values.0 + values.1 + values.2))
/// """)
/// }
/// // Prints "Rolled: 2, 4, 3 (total: 9)" (or other random values).
///
/// - Parameters:
/// - keyPath0: The key path of a property on `Output`
/// - keyPath1: The key path of another property on `Output`
/// - keyPath2: The key path of a third property on `Output`
/// - keyPath0: The key path of a property on `Output`.
/// - keyPath1: The key path of a second property on `Output`.
/// - keyPath2: The key path of a third property on `Output`.
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
public func map<Result0, Result1, Result2>(
_ keyPath0: KeyPath<Output, Result0>,
+24 -3
View File
@@ -7,10 +7,11 @@
/// A publisher that emits an output to each subscriber just once, and then finishes.
///
/// You can use a `Just` publisher to start a chain of publishers. A `Just` publisher
/// is also useful when replacing a value with `Catch`.
/// You can use a `Just` publisher to start a chain of publishers. A `Just` publisher is
/// also useful when replacing a value with `Publishers.Catch`.
///
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
/// In contrast with `Result.Publisher`, a `Just` publisher cant fail with an error.
/// And unlike `Optional.Publisher`, a `Just` publisher always produces a value.
public struct Just<Output>: Publisher {
public typealias Failure = Never
@@ -249,6 +250,26 @@ extension Just {
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(Result { try nextPartialResult(initialResult, output) })
}
public func prepend(_ elements: Output...) -> Publishers.Sequence<[Output], Never> {
return prepend(elements)
}
public func prepend<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Sequence<[Output], Never> where Output == Elements.Element {
return .init(sequence: elements + [output])
}
public func append(_ elements: Output...) -> Publishers.Sequence<[Output], Never> {
return append(elements)
}
public func append<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Sequence<[Output], Never> where Output == Elements.Element {
return .init(sequence: [output] + elements)
}
}
extension Just {
@@ -1,25 +0,0 @@
//
// OperatorSubscription.swift
//
//
// Created by Sergej Jaskiewicz on 26.06.2019.
//
internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
internal var downstream: Downstream?
internal var upstreamSubscription: Subscription?
internal var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
internal init(downstream: Downstream) {
self.downstream = downstream
}
internal func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
downstream = nil
}
}
@@ -28,28 +28,45 @@ extension Optional {
self.optional = optional
}
/// A publisher that publishes an optional value to each subscriber
/// exactly once, if the optional has a value.
public var publisher: Publisher {
return Publisher(optional)
}
/// The type of a Combine publisher that publishes the value of a Swift optional
/// instance to each subscriber exactly once, if the instance has any value at
/// all.
///
/// In contrast with `Just`, an `Optional` publisher may send
/// no value before completion.
/// In contrast with the `Just` publisher, which always produces a single value,
/// this publisher might not send any values and instead finish normally,
/// if `output` is `nil`.
public struct Publisher: OpenCombine.Publisher {
/// The kind of value published by this publisher.
///
/// This publisher produces the type wrapped by the optional.
public typealias Output = Wrapped
/// The kind of error this publisher might publish.
///
/// The optional publisher never produces errors.
public typealias Failure = Never
/// The result to deliver to each subscriber.
/// The output to deliver to each subscriber.
public let output: Wrapped?
/// Creates a publisher to emit the optional value of a successful result,
/// or fail with an error.
/// Creates a publisher to emit the value of the optional, or to finish
/// immediately if the optional doesn't have a value.
///
/// - Parameter result: The result to deliver to each subscriber.
/// - Parameter output: The result to deliver to each subscriber.
public init(_ output: Output?) {
self.output = output
}
/// Implements the Publisher protocol by accepting the subscriber and
/// immediately publishing the optionals value if it has one, or finishing
/// normally if it doesnt.
///
/// - Parameter subscriber: The subscriber to add.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
@@ -64,13 +81,23 @@ extension Optional {
}
}
public var ocombine: OCombine {
return .init(self)
}
#if !canImport(Combine)
/// A publisher that publishes an optional value to each subscriber
/// exactly once, if the optional has a value.
/// The type of a Combine publisher that publishes the value of a Swift optional
/// instance to each subscriber exactly once, if the instance has any value at
/// all.
///
/// In contrast with `Just`, an `Optional` publisher may send
/// no value before completion.
/// In contrast with the `Just` publisher, which always produces a single value,
/// this publisher might not send any values and instead finish normally,
/// if `output` is `nil`.
public typealias Publisher = OCombine.Publisher
public var publisher: Publisher {
return Publisher(self)
}
#endif
}
@@ -235,11 +262,13 @@ extension Optional.OCombine.Publisher {
in range: RangeExpression
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
let range = range.relative(to: 0 ..< Int.max)
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
// I don't know why, but Combine has this precondition
precondition(range.upperBound < .max - 1)
return .init(output.flatMap { range.contains(0) ? $0 : nil })
return .init(
output.flatMap { (range.lowerBound == 0 && range.upperBound != 0) ? $0 : nil }
)
}
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
@@ -10,13 +10,28 @@ extension Publisher {
/// Publishes a single Boolean value that indicates whether all received elements pass
/// a given predicate.
///
/// When this publisher receives an element, it runs the predicate against
/// the element. If the predicate returns `false`, the publisher produces a `false`
/// value and finishes. If the upstream publisher finishes normally, this publisher
/// produces a `true` value and finishes.
/// As a `reduce`-style operator, this publisher produces at most one value.
/// Backpressure note: Upon receiving any request greater than zero, this publisher
/// requests unlimited elements from the upstream publisher.
/// Use the `allSatisfy(_:)` operator to determine if all elements in a stream satisfy
/// a criteria you provide. When this publisher receives an element, it runs
/// the predicate against the element. If the predicate returns `false`, the publisher
/// produces a `false` value and finishes. If the upstream publisher finishes
/// normally, this publisher produces a `true` value and finishes.
///
/// In the example below, the `allSatisfy(_:)` operator tests if each an integer array
/// publishers elements fall into the `targetRange`:
///
/// let targetRange = (-1...100)
/// let numbers = [-1, 0, 10, 5]
/// numbers.publisher
/// .allSatisfy { targetRange.contains($0) }
/// .sink { print("\($0)") }
///
/// // Prints: "true"
///
/// With operators similar to `reduce(_:_:)`, this publisher produces at most one
/// value.
///
/// > Note: Upon receiving any request greater than zero, this publisher requests
/// unlimited elements from the upstream publisher.
///
/// - Parameter predicate: A closure that evaluates each received element.
/// Return `true` to continue, or `false` to cancel the upstream and complete.
@@ -31,21 +46,45 @@ extension Publisher {
/// Publishes a single Boolean value that indicates whether all received elements pass
/// a given error-throwing predicate.
///
/// When this publisher receives an element, it runs the predicate against
/// the element. If the predicate returns `false`, the publisher produces a `false`
/// value and finishes. If the upstream publisher finishes normally, this publisher
/// produces a `true` value and finishes. If the predicate throws an error,
/// the publisher fails, passing the error to its downstream.
/// As a `reduce`-style operator, this publisher produces at most one value.
/// Backpressure note: Upon receiving any request greater than zero, this publisher
/// requests unlimited elements from the upstream publisher.
/// Use the `tryAllSatisfy(_:)` operator to determine if all elements in a stream
/// satisfy a criteria in an error-throwing predicate you provide. When this publisher
/// receives an element, it runs the predicate against the element. If the predicate
/// returns `false`, the publisher produces a `false` value and finishes.
/// If the upstream publisher finishes normally, this publisher produces a `true`
/// value and finishes. If the predicate throws an error, the publisher fails and
/// passes the error to its downstream subscriber.
///
/// - Parameter predicate: A closure that evaluates each received element.
/// Return `true` to continue, or `false` to cancel the upstream and complete.
/// The closure may throw, in which case the publisher cancels the upstream
/// publisher and fails with the thrown error.
/// - Returns: A publisher that publishes a Boolean value that indicates whether
/// all received elements pass a given predicate.
/// In the example below, an error-throwing predicate tests if each of an integer
/// array publishers elements fall into the `targetRange`; the predicate throws
/// an error if an element is zero and terminates the stream.
///
/// let targetRange = (-1...100)
/// let numbers = [-1, 10, 5, 0]
///
/// numbers.publisher
/// .tryAllSatisfy { anInt in
/// guard anInt != 0 else { throw RangeError() }
/// return targetRange.contains(anInt)
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("value: \($0)") }
/// )
///
/// // Prints: "completion: failure(RangeError())"
///
/// With operators similar to `reduce(_:_:)`, this publisher produces at most one
/// value.
///
/// > Note: Upon receiving any request greater than zero, this publisher requests
/// unlimited elements from the upstream publisher.
///
/// - Parameter predicate: A closure that evaluates each received element. Return
/// `true` to continue, or `false` to cancel the upstream and complete. The closure
/// may throw an error, in which case the publisher cancels the upstream publisher
/// and fails with the thrown error.
/// - Returns: A publisher that publishes a Boolean value that indicates whether all
/// received elements pass a given predicate.
public func tryAllSatisfy(
_ predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryAllSatisfy<Self> {
@@ -0,0 +1,158 @@
//
// Publishers.AssertNoFailure.swift
//
//
// Created by Sergej Jaskiewicz on 25.12.2019.
//
extension Publisher {
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes
/// all received input.
///
/// Use `assertNoFailure()` for internal sanity checks that are active during testing.
/// However, it is important to note that, like its Swift counterpart
/// `fatalError(_:)`, the `assertNoFailure()` operator asserts a fatal exception when
/// triggered in both development/testing _and_ shipping versions of code.
///
/// In the example below, a `CurrentValueSubject` publishes the initial and second
/// values successfully. The third value, containing a `genericSubjectError`, causes
/// the `assertNoFailure()` operator to assert a fatal exception stopping the process:
///
/// public enum SubjectError: Error {
/// case genericSubjectError
/// }
///
/// let subject = CurrentValueSubject<String, Error>("initial value")
/// subject
/// .assertNoFailure()
/// .sink(receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("value: \($0).") }
/// )
///
/// subject.send("second value")
/// subject.send(completion: .failure(SubjectError.genericSubjectError))
///
/// // Prints:
/// // value: initial value.
/// // value: second value.
/// // The process then terminates in the debugger as the assertNoFailure
/// // operator catches the genericSubjectError.
///
/// - Parameters:
/// - prefix: A string used at the beginning of the fatal error message.
/// - file: A filename used in the error message. This defaults to `#file`.
/// - line: A line number used in the error message. This defaults to `#line`.
/// - Returns: A publisher that raises a fatal error when its upstream publisher
/// fails.
public func assertNoFailure(_ prefix: String = "",
file: StaticString = #file,
line: UInt = #line) -> Publishers.AssertNoFailure<Self> {
return .init(upstream: self, prefix: prefix, file: file, line: line)
}
}
extension Publishers {
/// A publisher that raises a fatal error upon receiving any failure, and otherwise
/// republishes all received input.
///
/// Use this function for internal sanity checks that are active during testing but
/// do not impact performance of shipping code.
public struct AssertNoFailure<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Never
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The string used at the beginning of the fatal error message.
public let prefix: String
/// The filename used in the error message.
public let file: StaticString
/// The line number used in the error message.
public let line: UInt
public init(upstream: Upstream, prefix: String, file: StaticString, line: UInt) {
self.upstream = upstream
self.prefix = prefix
self.file = file
self.line = line
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Never
{
upstream.subscribe(Inner(downstream: subscriber,
prefix: prefix,
file: file,
line: line))
}
}
}
extension Publishers.AssertNoFailure {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Never
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let prefix: String
private let file: StaticString
private let line: UInt
let combineIdentifier = CombineIdentifier()
init(downstream: Downstream, prefix: String, file: StaticString, line: UInt) {
self.downstream = downstream
self.prefix = prefix
self.file = file
self.line = line
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure(let error):
let prefix = self.prefix.isEmpty ? "" : self.prefix + ": "
fatalError("\(prefix)\(error)", file: file, line: line)
}
}
var description: String { return "AssertNoFailure" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("file", file),
("line", line),
("prefix", prefix)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -5,21 +5,25 @@
// 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()`.
/// such as `TimerPublisher` in `OpenCombineFoundation`.
///
/// let autoconnectedPublisher = somePublisher
/// .makeConnectable()
/// In the following example, the `Timer.publish()` operator creates
/// a `TimerPublisher`, which is a `ConnectablePublisher`. As a result, subscribers
/// dont receive any values until after a call to `connect()`.
/// For convenience when working with a single subscriber, the `.autoconnect()`
/// operator performs the `connect()` call when attached to by the subscriber.
///
/// cancellable = Timer.publish(every: 1, on: .main, in: .default)
/// .autoconnect()
/// .subscribe(someSubscriber)
///
/// .sink { date in
/// print ("Date now: \(date)")
/// }
/// - Returns: A publisher which automatically connects to its upstream connectable
/// publisher.
public func autoconnect() -> Publishers.Autoconnect<Self> {
@@ -29,12 +33,12 @@ extension ConnectablePublisher {
extension Publishers {
/// A publisher that automatically connects and disconnects from this connectable
/// publisher.
/// A publisher that automatically connects to an upstream connectable publisher.
///
/// This publisher calls `connect()` on the upstream `ConnectablePublisher` when first
/// attached to by a subscriber.
public class Autoconnect<Upstream: ConnectablePublisher>: Publisher {
// NOTE: This class has been audited for thread safety
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
@@ -0,0 +1,231 @@
//
// Publishers.Breakpoint.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
#if !WASI
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Raises a debugger signal when a provided closure needs to stop the process in
/// the debugger.
///
/// Use `breakpoint(receiveSubscription:receiveOutput:receiveCompletion:)` to examine
/// one or more stages of the subscribe/publish/completion process and stop in
/// the debugger, based on conditions you specify. When any of the provided closures
/// returns `true`, this operator raises the `SIGTRAP` signal to stop the process
/// in the debugger. Otherwise, this publisher passes through values and completions
/// as-is.
///
/// In the example below, a `PassthroughSubject` publishes strings to a breakpoint
/// republisher. When the breakpoint receives the string `DEBUGGER`, it returns
/// `true`, which stops the app in the debugger.
///
/// let publisher = PassthroughSubject<String?, Never>()
/// cancellable = publisher
/// .breakpoint(
/// receiveOutput: { value in return value == "DEBUGGER" }
/// )
/// .sink { print("\(String(describing: $0))" , terminator: " ") }
///
/// publisher.send("DEBUGGER")
///
/// // Prints: "error: Execution was interrupted, reason: signal SIGTRAP."
/// // Depending on your specific environment, the console messages may
/// // also include stack trace information, which is not shown here.
///
/// - Parameters:
/// - receiveSubscription: A closure that executes when when the publisher receives
/// a subscription. Return `true` from this closure to raise `SIGTRAP`, or `false`
/// to continue.
/// - receiveOutput: A closure that executes when when the publisher receives
/// a value. Return `true` from this closure to raise `SIGTRAP`, or `false`
/// to continue.
/// - receiveCompletion: A closure that executes when when the publisher receives
/// a completion. Return `true` from this closure to raise `SIGTRAP`, or `false`
/// to continue.
/// - Returns: A publisher that raises a debugger signal when one of the provided
/// closures returns `true`.
public func breakpoint(
receiveSubscription: ((Subscription) -> Bool)? = nil,
receiveOutput: ((Output) -> Bool)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Bool)? = nil
) -> Publishers.Breakpoint<Self> {
return .init(upstream: self,
receiveSubscription: receiveSubscription,
receiveOutput: receiveOutput,
receiveCompletion: receiveCompletion)
}
/// Raises a debugger signal upon receiving a failure.
///
/// When the upstream publisher fails with an error, this publisher raises
/// the `SIGTRAP` signal, which stops the process in the debugger. Otherwise, this
/// publisher passes through values and completions as-is.
///
/// In this example a `PassthroughSubject` publishes strings, but its downstream
/// `Publisher/tryMap(_:)` operator throws an error. This sends the error downstream
/// as a `Subscribers.Completion.failure(_:)`. The `breakpointOnError()`
/// operator receives this completion and stops the app in the debugger.
///
/// struct CustomError : Error {}
/// let publisher = PassthroughSubject<String?, Error>()
/// cancellable = publisher
/// .tryMap { stringValue in
/// throw CustomError()
/// }
/// .breakpointOnError()
/// .sink(
/// receiveCompletion: { completion in
/// print("Completion: \(String(describing: completion))")
/// },
/// receiveValue: { aValue in
/// print("Result: \(String(describing: aValue))")
/// }
/// )
///
/// publisher.send("TEST DATA")
///
/// // Prints: "error: Execution was interrupted, reason: signal SIGTRAP."
/// // Depending on your specific environment, the console messages may
/// // also include stack trace information, which is not shown here.
///
/// - Returns: A publisher that raises a debugger signal upon receiving a failure.
public func breakpointOnError() -> Publishers.Breakpoint<Self> {
return breakpoint(receiveCompletion: { completion in
switch completion {
case .finished:
return false
case .failure:
return true
}
})
}
}
extension Publishers {
/// A publisher that raises a debugger signal when a provided closure needs to stop
/// the process in the debugger.
///
/// When any of the provided closures returns `true`, this publisher raises
/// the `SIGTRAP` signal to stop the process in the debugger.
/// Otherwise, this publisher passes through values and completions as-is.
public struct Breakpoint<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that executes when the publisher receives a subscription, and can
/// raise a debugger signal by returning a `true` Boolean value.
public let receiveSubscription: ((Subscription) -> Bool)?
/// A closure that executes when the publisher receives output from the upstream
/// publisher, and can raise a debugger signal by returning a `true` Boolean
/// value.
public let receiveOutput: ((Upstream.Output) -> Bool)?
/// A closure that executes when the publisher receives completion, and can raise
/// a debugger signal by returning a `true` Boolean value.
public let receiveCompletion:
((Subscribers.Completion<Upstream.Failure>) -> Bool)?
/// Creates a breakpoint publisher with the provided upstream publisher and
/// breakpoint-raising closures.
///
/// - Parameters:
/// - upstream: The publisher from which this publisher receives elements.
/// - receiveSubscription: A closure that executes when the publisher receives
/// a subscription, and can raise a debugger signal by returning a `true`
/// Boolean value.
/// - receiveOutput: A closure that executes when the publisher receives output
/// from the upstream publisher, and can raise a debugger signal by returning
/// a `true` Boolean value.
/// - receiveCompletion: A closure that executes when the publisher receives
/// completion, and can raise a debugger signal by returning a `true` Boolean
/// value.
public init(
upstream: Upstream,
receiveSubscription: ((Subscription) -> Bool)? = nil,
receiveOutput: ((Upstream.Output) -> Bool)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Bool)? = nil
) {
self.upstream = upstream
self.receiveSubscription = receiveSubscription
self.receiveOutput = receiveOutput
self.receiveCompletion = receiveCompletion
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(self, downstream: subscriber))
}
}
}
extension Publishers.Breakpoint {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let breakpoint: Publishers.Breakpoint<Upstream>
let combineIdentifier = CombineIdentifier()
init(_ breakpoint: Publishers.Breakpoint<Upstream>,
downstream: Downstream) {
self.downstream = downstream
self.breakpoint = breakpoint
}
func receive(subscription: Subscription) {
if breakpoint.receiveSubscription?(subscription) == true {
__stopInDebugger()
}
downstream.receive(subscription: subscription)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
if breakpoint.receiveOutput?(input) == true {
__stopInDebugger()
}
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if breakpoint.receiveCompletion?(completion) == true {
__stopInDebugger()
}
downstream.receive(completion: completion)
}
var description: String { return "Breakpoint" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
#endif // !WASI
@@ -0,0 +1,358 @@
//
// Publishers.Buffer.swift
//
//
// Created by Sergej Jaskiewicz on 08.01.2020.
//
extension Publisher {
/// Buffers elements received from an upstream publisher.
///
/// Use `buffer(size:prefetch:whenFull:)` to collect a specific number of elements
/// from an upstream publisher before republishing them to the downstream subscriber
/// according to the `Publishers.BufferingStrategy` and `Publishers.PrefetchStrategy`
/// strategy you specify.
///
/// If the publisher completes before reaching the `size` threshold, it buffers
/// the elements and publishes them downstream prior to completion.
///
/// - Parameters:
/// - size: The maximum number of elements to store.
/// - prefetch: The strategy to initially populate the buffer.
/// - whenFull: The action to take when the buffer becomes full.
/// - Returns: A publisher that buffers elements received from an upstream publisher.
public func buffer(
size: Int,
prefetch: Publishers.PrefetchStrategy,
whenFull: Publishers.BufferingStrategy<Failure>
) -> Publishers.Buffer<Self> {
return .init(upstream: self,
size: size,
prefetch: prefetch,
whenFull: whenFull)
}
}
extension Publishers {
/// A strategy for filling a buffer.
public enum PrefetchStrategy {
/// A strategy to fill the buffer at subscription time, and keep it full
/// thereafter.
///
/// This strategy starts by making a demand equal to the buffers size from
/// the upstream when the subscriber first connects. Afterwards, it continues
/// to demand elements from the upstream to try to keep the buffer full.
case keepFull
/// A strategy that avoids prefetching and instead performs requests on demand.
///
/// This strategy just forwards the downstreams requests to the upstream
/// publisher.
case byRequest
}
/// A strategy that handles exhaustion of a buffers capacity.
public enum BufferingStrategy<Failure: Error> {
/// When the buffer is full, discard the newly received element.
case dropNewest
/// When the buffer is full, discard the oldest element in the buffer.
case dropOldest
/// When the buffer is full, execute the closure to provide a custom error.
case customError(() -> Failure)
}
/// A publisher that buffers elements received from an upstream publisher.
public struct Buffer<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The maximum number of elements to store.
public let size: Int
/// The strategy for initially populating the buffer.
public let prefetch: Publishers.PrefetchStrategy
/// The action to take when the buffer becomes full.
public let whenFull: Publishers.BufferingStrategy<Failure>
/// Creates a publisher that buffers elements received from an upstream publisher.
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
/// - Parameter size: The maximum number of elements to store.
/// - Parameter prefetch: The strategy for initially populating the buffer.
/// - Parameter whenFull: The action to take when the buffer becomes full.
public init(upstream: Upstream,
size: Int,
prefetch: Publishers.PrefetchStrategy,
whenFull: Publishers.BufferingStrategy<Failure>) {
self.upstream = upstream
self.size = size
self.prefetch = prefetch
self.whenFull = whenFull
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber,
size: size,
prefetch: prefetch,
whenFull: whenFull)
upstream.subscribe(inner)
}
}
}
extension Publishers.PrefetchStrategy: Equatable {}
extension Publishers.PrefetchStrategy: Hashable {}
extension Publishers.Buffer {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let lock = UnfairLock.allocate()
private var recursion = false
private let size: Int
private let prefetch: Publishers.PrefetchStrategy // keepFull is 0x0
private let whenFull: Publishers.BufferingStrategy<Failure>
private let downstream: Downstream
private var state = SubscriptionStatus.awaitingSubscription
private var downstreamDemand = Subscribers.Demand.none
// TODO: Use a deque here?
// Need to measure performance with large buffers and `dropOldest` strategy.
private var values = [Input]()
private var upstreamFailed = false
private var terminal: Subscribers.Completion<Failure>?
init(downstream: Downstream,
size: Int,
prefetch: Publishers.PrefetchStrategy,
whenFull: Publishers.BufferingStrategy<Failure>) {
self.size = size
self.prefetch = prefetch
self.whenFull = whenFull
self.downstream = downstream
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscription)
lock.unlock()
let upstreamDemand: Subscribers.Demand
switch prefetch {
case .keepFull:
upstreamDemand = .max(size)
case .byRequest:
upstreamDemand = .unlimited
}
subscription.request(upstreamDemand)
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return .none
}
switch terminal {
case nil, .finished?:
if values.count >= size {
switch whenFull {
case .dropNewest:
lock.unlock()
return drain()
case .dropOldest:
values.removeFirst()
case let .customError(makeError):
terminal = .failure(makeError())
lock.unlock()
subscription.cancel()
return .none
}
}
values.append(input)
lock.unlock()
return drain()
case .failure?:
lock.unlock()
return .none
}
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case .subscribed = state, terminal == nil else {
lock.unlock()
return
}
terminal = completion
lock.unlock()
_ = drain()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
downstreamDemand += demand
let recursion = self.recursion
lock.unlock()
if recursion {
return
}
let more = drain()
if more != .none {
// Request the number of items just enough to fill the buffer.
subscription.request(more)
}
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
state = .terminal
values = []
lock.unlock()
subscription.cancel()
}
private func drain() -> Subscribers.Demand {
var upstreamDemand = Subscribers.Demand.none
lock.lock()
while true {
guard case .subscribed = state else {
lock.unlock()
return upstreamDemand
}
if downstreamDemand > 0 {
if values.isEmpty {
if let completion = terminal {
state = .terminal
lock.unlock()
downstream.receive(completion: completion)
} else {
lock.unlock()
}
return upstreamDemand
}
} else {
if let completion = terminal, case .failure = completion {
state = .terminal
lock.unlock()
downstream.receive(completion: completion)
} else {
lock.unlock()
}
return upstreamDemand
}
let poppedValues = lockedPop(downstreamDemand)
assert(poppedValues.count > 0,
"""
We check that the buffer is not empty and downstreamDemand is \
nonzero, how can this be triggered?
""")
// This should not crash because `lockedPop(_:)` returns at most
// `downstreamDemand` items.
downstreamDemand -= poppedValues.count
recursion = true
lock.unlock()
var newDownstreamDemand = Subscribers.Demand.none
var additionalUpstreamDemand = 0
for value in poppedValues {
newDownstreamDemand += downstream.receive(value)
additionalUpstreamDemand += 1
}
if prefetch == .keepFull {
upstreamDemand += additionalUpstreamDemand
}
lock.lock()
recursion = false
downstreamDemand += newDownstreamDemand
}
}
private func lockedPop(_ demand: Subscribers.Demand) -> [Input] {
assert(demand > 0)
guard let max = demand.max else {
let poppedValues = self.values
self.values = []
return poppedValues
}
let poppedValues = Array(values.prefix(max))
values.removeFirst(poppedValues.count)
return poppedValues
}
var description: String { return "Buffer" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("values", values),
("state", state),
("downstreamDemand", downstreamDemand),
("terminal", terminal as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,454 @@
${template_header}
//
// Publishers.Catch.swift
//
//
// Created by Sergej Jaskiewicz on 25.12.2019.
//
%{
instantiations = ['Catch', 'TryCatch']
}%
extension Publisher {
/// Handles errors from an upstream publisher by replacing it with another publisher.
///
/// Use `catch()` to replace an error from an upstream publisher with a new publisher.
///
/// In the example below, the `catch()` operator handles the `SimpleError` thrown by
/// the upstream publisher by replacing the error with a `Just` publisher. This
/// continues the stream by publishing a single value and completing normally.
///
/// struct SimpleError: Error {}
/// let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
/// cancellable = numbers.publisher
/// .tryLast(where: {
/// guard $0 != 0 else { throw SimpleError() }
/// return true
/// })
/// .catch { error in
/// Just(-1)
/// }
/// .sink { print("\($0)") }
/// // Prints: -1
///
/// Backpressure note: This publisher passes through `request` and `cancel` to
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
/// demand to the new `Publisher`.
///
/// - SeeAlso: `replaceError`
/// - Parameter handler: A closure that accepts the upstream failure as input and
/// returns a publisher to replace the upstream publisher.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher.
public func `catch`<NewPublisher: Publisher>(
_ handler: @escaping (Failure) -> NewPublisher
) -> Publishers.Catch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
/// Handles errors from an upstream publisher by either replacing it with another
/// publisher or throwing a new error.
///
/// Use `tryCatch(_:)` to decide how to handle from an upstream publisher by either
/// replacing the publisher with a new publisher, or throwing a new error.
///
/// In the example below, an array publisher emits values that a `tryMap(_:)` operator
/// evaluates to ensure the values are greater than zero. If the values arent greater
/// than zero, the operator throws an error to the downstream subscriber to let it
/// know there was a problem. The subscriber, `tryCatch(_:)`, replaces the error with
/// a new publisher using ``Just`` to publish a final value before the stream ends
/// normally.
///
/// enum SimpleError: Error { case error }
/// var numbers = [5, 4, 3, 2, 1, -1, 7, 8, 9, 10]
///
/// cancellable = numbers.publisher
/// .tryMap { v in
/// if v > 0 {
/// return v
/// } else {
/// throw SimpleError.error
/// }
/// }
/// .tryCatch { error in
/// Just(0) // Send a final value before completing normally.
/// // Alternatively, throw a new error to terminate the stream.
/// }
/// .sink(receiveCompletion: { print ("Completion: \($0).") },
/// receiveValue: { print ("Received \($0).") })
/// // Received 5.
/// // Received 4.
/// // Received 3.
/// // Received 2.
/// // Received 1.
/// // Received 0.
/// // Completion: finished.
///
/// - Parameter handler: A throwing closure that accepts the upstream failure as
/// input. This closure can either replace the upstream publisher with a new one,
/// or throw a new error to the downstream subscriber.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher, or an error.
public func tryCatch<NewPublisher: Publisher>(
_ handler: @escaping (Failure) throws -> NewPublisher
) -> Publishers.TryCatch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
}
extension Publishers {
/// A publisher that handles errors from an upstream publisher by replacing the failed
/// publisher with another publisher.
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = NewPublisher.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that accepts the upstream failure as input and returns a publisher
/// to replace the upstream publisher.
public let handler: (Upstream.Failure) -> NewPublisher
/// Creates a publisher that handles errors from an upstream publisher by
/// replacing the failed publisher with another publisher.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - handler: A closure that accepts the upstream failure as input and returns
/// a publisher to replace the upstream publisher.
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
/// A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher or producing a new error.
///
/// Because this publishers handler can throw an error, `Publishers.TryCatch` defines
/// its `Failure` type as `Error`. This is different from `Publishers.Catch`, which
/// gets its failure type from the replacement publisher.
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that accepts the upstream failure as input and either returns
/// a publisher to replace the upstream publisher or throws an error.
public let handler: (Upstream.Failure) throws -> NewPublisher
/// Creates a publisher that handles errors from an upstream publisher by
/// replacing the failed publisher with another publisher or by throwing an error.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - handler: A closure that accepts the upstream failure as input and either
/// returns a publisher to replace the upstream publisher. If this closure
/// throws an error, the publisher terminates with the thrown error.
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
}
% for instantiation in instantiations:
% throws_modifier = ' throws' if instantiation == 'TryCatch' else ''
extension Publishers.${instantiation} {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output,
% if instantiation == 'Catch':
Downstream.Failure == NewPublisher.Failure
% else:
Downstream.Failure == Error
% end
{
struct UncaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePre(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePre(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
return inner.receivePre(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
struct CaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = NewPublisher.Output
typealias Failure = NewPublisher.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePost(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePost(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
inner.receivePost(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
private enum State {
case pendingPre
case pre(Subscription)
case pendingPost
case post(Subscription)
case cancelled
}
private let lock = UnfairLock.allocate()
private var demand = Subscribers.Demand.none
private var state = State.pendingPre
private let downstream: Downstream
private let handler: (Upstream.Failure)${throws_modifier} -> NewPublisher
init(downstream: Downstream,
handler: @escaping (Upstream.Failure)${throws_modifier} -> NewPublisher) {
self.downstream = downstream
self.handler = handler
}
deinit {
lock.deallocate()
}
func receivePre(subscription: Subscription) {
lock.lock()
guard case .pendingPre = state else {
lock.unlock()
subscription.cancel()
return
}
state = .pre(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
let newDemand = downstream.receive(input)
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
lock.lock()
switch state {
case .pre:
state = .cancelled
lock.unlock()
downstream.receive(completion: .finished)
case .pendingPre, .pendingPost, .post, .cancelled:
lock.unlock()
}
case .failure(let error):
lock.lock()
switch state {
case .pre:
state = .pendingPost
lock.unlock()
% if instantiation == 'Catch':
handler(error).subscribe(CaughtS(inner: self))
% else:
do {
try handler(error).subscribe(CaughtS(inner: self))
} catch let anotherError {
lock.lock()
state = .cancelled
lock.unlock()
downstream.receive(completion: .failure(anotherError))
}
% end
case .cancelled:
lock.unlock()
case .pendingPre, .post, .pendingPost:
completionBeforeSubscription()
}
}
}
func receivePost(subscription: Subscription) {
lock.lock()
guard case .pendingPost = state else {
lock.unlock()
subscription.cancel()
return
}
state = .post(subscription)
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
lock.lock()
guard case .post = state else {
lock.unlock()
return
}
state = .cancelled
lock.unlock()
% if instantiation == 'Catch':
downstream.receive(completion: completion)
% else:
downstream.receive(completion: completion.eraseError())
% end
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .pendingPre:
// The client is only able to call the `request` method after we've sent
// `self` downstream. We only do it in the `receivePre(subscription:)`
// method, after setting `state` to `pre`.
// After that `state` never becomes `pendingPre`.
requestBeforeSubscription()
case let .pre(subscription):
self.demand += demand
lock.unlock()
subscription.request(demand)
case .pendingPost:
self.demand += demand
lock.unlock()
case let .post(subscription):
lock.unlock()
subscription.request(demand)
case .cancelled:
lock.unlock()
}
}
func cancel() {
lock.lock()
switch state {
case let .pre(subscription), let .post(subscription):
state = .cancelled
lock.unlock()
subscription.cancel()
case .pendingPre, .pendingPost, .cancelled:
lock.unlock()
}
}
var description: String { return "${instantiation}" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
% end
private func completionBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: received completion but do not have subscription",
file: file,
line: line)
}
private func requestBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: request before subscription sent",
file: file,
line: line)
}
@@ -10,13 +10,30 @@ extension Publisher {
/// Collects all received elements, and emits a single array of the collection when
/// the upstream publisher finishes.
///
/// Use `collect()` to gather elements into an array that the operator emits after
/// the upstream publisher finishes.
///
/// If the upstream publisher fails with an error, this publisher forwards the error
/// to the downstream receiver instead of sending its output.
///
/// This publisher requests an unlimited number of elements from the upstream
/// publisher. It only sends the collected array to its downstream after a request
/// whose demand is greater than 0 items.
/// Note: This publisher uses an unbounded amount of memory to store the received
/// values.
/// publisher and uses an unbounded amount of memory to store the received values.
/// The publisher may exert memory pressure on the system for very large sets of
/// elements.
///
/// The `collect()` operator only sends the collected array to its downstream receiver
/// after a request whose demand is greater than 0 items. Otherwise, `collect()` waits
/// until it receives a non-zero request.
///
/// In the example below, an Integer range is a publisher that emits an array of
/// integers:
///
/// let numbers = (0...10)
/// cancellable = numbers.publisher
/// .collect()
/// .sink { print("\($0)") }
///
/// // Prints: "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
///
/// - Returns: A publisher that collects all received items and returns them as
/// an array upon completion.
@@ -0,0 +1,198 @@
//
// Publishers.CollectByCount.swift
//
//
// Created by Sergej Jaskiewicz on 24.12.2019.
//
extension Publisher {
/// Collects up to the specified number of elements, and then emits a single array of
/// the collection.
///
/// Use `collect(_:)` to emit arrays of at most `count` elements from an upstream
/// publisher. If the upstream publisher finishes before collecting the specified
/// number of elements, the publisher sends an array of only the items it received
/// This may be fewer than `count` elements.
///
/// If the upstream publisher fails with an error, this publisher forwards the error
/// to the downstream receiver instead of sending its output.
///
/// In the example below, the `collect(_:)` operator emits one partial and two full
/// arrays based on the requested collection size of `5`:
///
/// let numbers = (0...10)
/// cancellable = numbers.publisher
/// .collect(5)
/// .sink { print("\($0), terminator: " "") }
///
/// // Prints "[0, 1, 2, 3, 4] [5, 6, 7, 8, 9] [10] "
///
/// > Note: When this publisher receives a request for `.max(n)` elements, it requests
/// `.max(count * n)` from the upstream publisher.
///
/// - Parameter count: The maximum number of received elements to buffer before
/// publishing.
/// - Returns: A publisher that collects up to the specified number of elements, and
/// then publishes them as an array.
public func collect(_ count: Int) -> Publishers.CollectByCount<Self> {
return .init(upstream: self, count: count)
}
}
extension Publishers {
/// A publisher that buffers a maximum number of items.
public struct CollectByCount<Upstream: Publisher>: Publisher {
public typealias Output = [Upstream.Output]
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The maximum number of received elements to buffer before publishing.
public let count: Int
public init(upstream: Upstream, count: Int) {
self.upstream = upstream
self.count = count
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
upstream.subscribe(Inner(downstream: subscriber, count: count))
}
}
}
extension Publishers.CollectByCount: Equatable where Upstream: Equatable {}
extension Publishers.CollectByCount {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == [Upstream.Output],
Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let count: Int
private var buffer: [Input] = []
private var subscription: Subscription?
private var finished = false
private let lock = UnfairLock.allocate()
init(downstream: Downstream, count: Int) {
self.downstream = downstream
self.count = count
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
if finished || self.subscription != nil {
lock.unlock()
subscription.cancel()
return
}
self.subscription = subscription
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
if subscription == nil {
lock.unlock()
return .none
}
buffer.append(input)
guard buffer.count == count else {
lock.unlock()
return .none
}
let output = self.buffer
self.buffer = []
lock.unlock()
return downstream.receive(output) * count
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
subscription = nil
finished = true
switch completion {
case .finished:
if buffer.isEmpty {
lock.unlock()
} else {
let buffer = self.buffer
self.buffer = []
lock.unlock()
_ = downstream.receive(buffer)
}
case .failure:
buffer = []
lock.unlock()
}
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
if let subscription = self.subscription {
lock.unlock()
subscription.request(demand * count)
} else {
lock.unlock()
}
}
func cancel() {
lock.lock()
if let subscription = self.subscription {
buffer = []
finished = true
self.subscription = nil
lock.unlock()
subscription.cancel()
} else {
lock.unlock()
}
}
var description: String { return "CollectByCount" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("downstream", downstream),
("upstreamSubscription", subscription as Any),
("buffer", buffer),
("count", count)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,268 @@
${template_header}
//
// Publishers.CombineLatest.swift.gyb
//
//
// Created by Sergej Jaskiewicz on 10.12.2019.
//
%{
from gyb_opencombine_support import (
suffix_variadic,
list_with_suffix_variadic,
indent
)
import string
instantiations = [(2, 'two', 'A second'),
(3, 'three', 'A third'),
(4, 'four', 'A fourth')]
def make_publisher_name(arity):
return suffix_variadic('CombineLatest', arity, arity - 1)
def make_upstream_types(arity, start=0):
return [str(c) for c in string.ascii_uppercase[start:(start + arity)]]
def make_upstream_generic_constraints(upstream_types, first_is_self=False):
format_string = '{0}Failure == {1}.Failure'
def format(i):
return format_string.format(upstream_types[i] + '.',
upstream_types[i + 1])
result = [format(i) for i in range(len(upstream_types) - 1)]
if first_is_self:
result.insert(0, format_string.format('', upstream_types[0]))
return result
def declare_combine_latest_method(arity, transform):
arg_count = arity - 1
declaration_format = """\
public func combineLatest<{}>(
{}
) -> {}
where {}\
"""
upstream_types = make_upstream_types(arg_count, 15)
method_generic_params = \
[upstream_type + ': Publisher' for upstream_type in upstream_types]
if transform:
method_generic_params.append('Result')
cs_method_generic_params = ', '.join(method_generic_params)
method_args = ['_ other: P'] if arg_count == 1 \
else ['_ publisher{}: {}'.format(i + 1, upstream_types[i]) \
for i in range(arg_count)]
if transform:
output_types = ['Output'] + ['{}.Output'.format(upstream_type) \
for upstream_type in upstream_types]
cs_output_types = ', '.join(output_types)
method_args \
.append('_ transform: @escaping ({}) -> Result'.format(cs_output_types))
cs_method_args = ',\n '.join(method_args)
publisher_generic_params = ['Self'] + upstream_types
cs_publisher_generic_params = ', '.join(publisher_generic_params)
publisher_name = 'Publishers.{}<{}>'.format(make_publisher_name(arity),
cs_publisher_generic_params)
if transform:
publisher_name = 'Publishers.Map<{}, Result>'.format(publisher_name)
generic_constraints = make_upstream_generic_constraints(upstream_types,
first_is_self=True)
cs_generic_constraints = ',\n '.join(generic_constraints)
declaration = declaration_format.format(cs_method_generic_params,
cs_method_args,
publisher_name,
cs_generic_constraints)
return indent(declaration, 4)
}%
// swiftlint:disable generic_type_name
// swiftlint:disable large_tuple
// MARK: - CombineLatest methods on Publisher
extension Publisher {
% for arity, _, _ in instantiations:
%
% argument_names = ['other'] \
% if arity == 2 else ['publisher{}'.format(i) for i in range(1, arity)]
% doc_cardinal = 'an additional publisher' if arity == 2 \
% else '{} additional publishers'.format(instantiations[arity - 3][1])
/// Subscribes to ${doc_cardinal} and publishes a tuple upon
/// receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
% for i in range(arity - 1):
% param_doc = 'Another' if arity == 2 else instantiations[i][2]
/// - ${argument_names[i]}: ${param_doc} publisher to combine with this one.
% end
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
${declare_combine_latest_method(arity, transform=False)}
{
return .init(self, ${', '.join(argument_names)})
}
/// Subscribes to ${doc_cardinal} and invokes a closure
/// upon receiving output from either publisher.
///
/// The combined publisher passes through any requests to *all* upstream publishers.
/// However, it still obeys the demand-fulfilling rule of only sending the request
/// amount downstream. If the demand isnt `.unlimited`, it drops values from upstream
/// publishers. It implements this by using a buffer size of 1 for each upstream, and
/// holds the most recent value in each buffer.
/// All upstream publishers need to finish for this publisher to finsh. If an upstream
/// publisher never publishes a value, this publisher never finishes.
/// If any of the combined publishers terminates with a failure, this publisher also
/// fails.
///
/// - Parameters:
% for i in range(arity - 1):
% param_doc = 'Another' if arity == 2 else instantiations[i][2]
/// - ${argument_names[i]}: ${param_doc} publisher to combine with this one.
% end
/// - transform: A closure that receives the most recent value from each publisher
/// and returns a new value to publish.
/// - Returns: A publisher that receives and combines elements from this and another
/// publisher.
${declare_combine_latest_method(arity, transform=True)}
{
% publisher_name = make_publisher_name(arity)
return Publishers.${publisher_name}(self, ${', '.join(argument_names)}).map {
transform(${', '.join(['${}'.format(i) for i in range(arity)])})
}
}
% end
}
// MARK: - CombineLatest publishers
extension Publishers {
% for arity, cardinal, _ in instantiations:
% publisher_name = make_publisher_name(arity)
% upstream_types = make_upstream_types(arity)
%
% upstream_generic_params = \
% [upstream_type + ': Publisher' for upstream_type in upstream_types]
%
% cs_upstream_generic_params = ', '.join(upstream_generic_params)
%
% output_types = [upstream_type + '.Output' for upstream_type in upstream_types]
%
% cs_output_types = ', '.join(output_types)
%
% upstream_generic_constraints = \
% make_upstream_generic_constraints(upstream_types)
%
% cs_upstream_generic_constraints = \
% ',\n '.join(upstream_generic_constraints)
%
% init_args = ['_ {}: {}'.format(upstream_type.lower(), upstream_type) \
% for upstream_type in upstream_types]
% cs_init_args = ',\n '.join(init_args)
%
% self_fields = [upstream_type.lower() for upstream_type in upstream_types]
/// A publisher that receives and combines the latest elements from ${cardinal}
/// publishers.
public struct ${publisher_name}<${cs_upstream_generic_params}>
: Publisher
where ${cs_upstream_generic_constraints}
{
public typealias Output = (${cs_output_types})
public typealias Failure = ${upstream_types[0]}.Failure
% for upstream_type in upstream_types:
public let ${upstream_type.lower()}: ${upstream_type}
% end
public init(
${cs_init_args}
) {
% for self_field in self_fields:
self.${self_field} = ${self_field}
% end
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure,
Downstream.Input == Output
{
% cs_indented_output_types = (',\n' + (50 * ' ')).join(output_types)
typealias Inner = CombineLatest${arity}Inner<${cs_indented_output_types},
Failure,
Downstream>
let inner = Inner(downstream: subscriber, upstreamCount: ${arity})
% for i in range(arity):
${self_fields[i]}.subscribe(Inner.Side(index: ${i}, combiner: inner))
% end
subscriber.receive(subscription: inner)
}
}
% end
}
// MARK: - Equatable conformances
% for arity, _, _ in instantiations:
%
% publisher_name = make_publisher_name(arity)
%
% upstream_types = make_upstream_types(arity)
%
% constraints = [upstream_type + ': Equatable' for upstream_type in upstream_types]
% cs_constraints = ',\n'.join(constraints)
% cs_constraints = indent(cs_constraints, 8)
%
extension Publishers.${publisher_name}: Equatable
where
${cs_constraints} {}
% end
// MARK: - Inners
% for arity, _, _ in instantiations:
%
% publisher_name = make_publisher_name(arity)
%
% upstream_types = make_upstream_types(arity)
%
% input_types = ['Input{}'.format(i) for i in range(arity)]
%
% converters = ['values[{}] as! {}'.format(i, input_types[i]) for i in range(arity)]
% output_type = '({})'.format(', '.join(input_types))
private final class CombineLatest${arity}Inner<${(',\n' + (40 * ' ')).join(input_types)},
Failure,
Downstream: Subscriber>
: AbstractCombineLatest<${output_type}, Failure, Downstream>
where Downstream.Input == ${output_type},
Downstream.Failure == Failure
{
override func convert(values: [Any?]) -> (${', '.join(input_types)}) {
return (${',\n '.join(converters)})
}
}
% end
@@ -5,90 +5,81 @@
// Created by Sergej Jaskiewicz on 11.07.2019.
//
extension Publishers {
/// A publisher that republishes all non-`nil` results of calling a closure
/// with each received element.
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that receives values from the upstream publisher
/// and returns optional values.
public let transform: (Upstream.Output) -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, transform: catching(transform))
upstream.subscribe(inner)
}
}
/// A publisher that republishes all non-`nil` results of calling an error-throwing
/// closure with each received element.
public struct TryCompactMap<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// An error-throwing closure that receives values from the upstream publisher
/// and returns optional values.
///
/// If this closure throws an error, the publisher fails.
public let transform: (Upstream.Output) throws -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) throws -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, transform: catching(transform))
upstream.subscribe(inner)
}
}
}
extension Publisher {
/// Calls a closure with each received element and publishes any returned
/// optional that has a value.
/// Calls a closure with each received element and publishes any returned optional
/// that has a value.
///
/// - Parameter transform: A closure that receives a value and returns
/// an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling
/// the transform closure.
/// OpenCombines `compactMap(_:)` operator performs a function similar to that of
/// `compactMap(_:)` in the Swift standard library: the `compactMap(_:)` operator in
/// OpenCombine removes `nil` elements in a publishers stream and republishes
/// non-`nil` elements to the downstream subscriber.
///
/// The example below uses a range of numbers as the source for a collection based
/// publisher. The `compactMap(_:)` operator consumes each element from the `numbers`
/// publisher attempting to access the dictionary using the element as the key.
/// If the examples dictionary returns a `nil`, due to a non-existent key,
/// `compactMap(_:)` filters out the `nil` (missing) elements.
///
/// let numbers = (0...5)
/// let romanNumeralDict: [Int : String] =
/// [1: "I", 2: "II", 3: "III", 5: "V"]
///
/// cancellable = numbers.publisher
/// .compactMap { romanNumeralDict[$0] }
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "I II III V"
///
/// - Parameter transform: A closure that receives a value and returns an optional
/// value.
/// - Returns: Any non-`nil` optional results of the calling the supplied closure.
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult?
) -> Publishers.CompactMap<Self, ElementOfResult> {
return .init(upstream: self, transform: transform)
}
/// Calls an error-throwing closure with each received element and publishes
/// any returned optional that has a value.
/// Calls an error-throwing closure with each received element and publishes any
/// returned optional that has a value.
///
/// If the closure throws an error, the publisher cancels the upstream and sends
/// the thrown error to the downstream receiver as a `Failure`.
/// Use `tryCompactMap(_:)` to remove `nil` elements from a publishers stream based
/// on an error-throwing closure you provide. If the closure throws an error,
/// the publisher cancels the upstream publisher and sends the thrown error to
/// the downstream subscriber as a `Publisher.Failure`.
///
/// - Parameter transform: an error-throwing closure that receives a value
/// and returns an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling
/// the `transform` closure.
/// The following example uses an array of numbers as the source for
/// a collection-based publisher. A `tryCompactMap(_:)` operator consumes each integer
/// from the publisher and uses a dictionary to transform the numbers from its Arabic
/// to Roman numerals, as an optional `String`.
///
/// If the closure called by `tryCompactMap(_:)` fails to look up a Roman numeral,
/// it returns the optional String `(unknown)`.
///
/// If the closure called by `tryCompactMap(_:)` determines the input is `0`, it
/// throws an error. The `tryCompactMap(_:)` operator catches this error and stops
/// publishing, sending a `Subscribers.Completion.failure(_:)` that wraps the error.
///
/// struct ParseError: Error {}
/// func romanNumeral(from: Int) throws -> String? {
/// let romanNumeralDict: [Int : String] =
/// [1: "I", 2: "II", 3: "III", 4: "IV", 5: "V"]
/// guard from != 0 else { throw ParseError() }
/// return romanNumeralDict[from]
/// }
/// let numbers = [6, 5, 4, 3, 2, 1, 0]
/// cancellable = numbers.publisher
/// .tryCompactMap { try romanNumeral(from: $0) }
/// .sink(
/// receiveCompletion: { print ("\($0)") },
/// receiveValue: { print ("\($0)", terminator: " ") }
/// )
///
/// // Prints: "(Unknown) V IV III II I failure(ParseError())"
///
/// - Parameter transform: An error-throwing closure that receives a value and returns
/// an optional value.
/// - Returns: Any non-`nil` optional results of calling the supplied closure.
public func tryCompactMap<ElementOfResult>(
_ transform: @escaping (Output) throws -> ElementOfResult?
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
@@ -123,89 +114,128 @@ extension Publishers.TryCompactMap {
}
}
private class _CompactMap<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscription
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Transform = (Input) -> Result<Downstream.Input?, Downstream.Failure>
extension Publishers {
fileprivate var _transform: Transform?
/// A publisher that republishes all non-`nil` results of calling a closure
/// with each received element.
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
var _isCompleted: Bool {
return _transform == nil
}
public typealias Failure = Upstream.Failure
init(downstream: Downstream, transform: @escaping Transform) {
_transform = transform
super.init(downstream: downstream)
}
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
/// A closure that receives values from the upstream publisher
/// and returns optional values.
public let transform: (Upstream.Output) -> Output?
func receive(_ input: Input) -> Subscribers.Demand {
guard let transform = _transform else { return .none }
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output?) {
self.upstream = upstream
self.transform = transform
}
switch transform(input) {
case .success(let output?):
return downstream.receive(output)
case .success(nil):
return .max(1)
case .failure(let error):
downstream.receive(completion: .failure(error))
_transform = nil
return .none
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
}
}
func request(_ demand: Subscribers.Demand) {
guard !_isCompleted else { return }
upstreamSubscription?.request(demand)
}
/// A publisher that republishes all non-`nil` results of calling an error-throwing
/// closure with each received element.
public struct TryCompactMap<Upstream: Publisher, Output>: Publisher {
override func cancel() {
_transform = nil
upstreamSubscription?.cancel()
upstreamSubscription = nil
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// An error-throwing closure that receives values from the upstream publisher
/// and returns optional values.
///
/// If this closure throws an error, the publisher fails.
public let transform: (Upstream.Output) throws -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) throws -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
}
}
}
extension Publishers.CompactMap {
private final class Inner<Downstream: Subscriber>
: _CompactMap<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Downstream.Failure == Upstream.Failure
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Failure == Downstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let filter: (Input) -> Downstream.Input?
let combineIdentifier = CombineIdentifier()
init(downstream: Downstream, filter: @escaping (Input) -> Downstream.Input?) {
self.downstream = downstream
self.filter = filter
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
if let output = filter(input) {
return downstream.receive(output)
}
return .max(1)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "CompactMap" }
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if !_isCompleted {
_transform = nil
downstream.receive(completion: completion)
}
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryCompactMap {
private final class Inner<Downstream: Subscriber>
: _CompactMap<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Downstream.Failure == Error
: FilterProducer<Downstream,
Upstream.Output,
Output,
Upstream.Failure,
(Upstream.Output) throws -> Output?>
where Downstream.Failure == Error, Downstream.Input == Output
{
var description: String { return "TryCompactMap" }
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if !_isCompleted {
_transform = nil
downstream.receive(completion: completion.eraseError())
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Output?, Error> {
do {
return try .continue(filter(newValue))
} catch {
return .failure(error)
}
}
override var description: String { return "TryCompactMap" }
}
}
@@ -10,6 +10,20 @@ extension Publisher where Output: Comparable {
/// Publishes the minimum value received from the upstream publisher, after it
/// finishes.
///
/// Use `min()` to find the minimum value in a stream of elements from
/// an upstream publisher.
///
/// In the example below, the `min()` operator emits a value when the publisher
/// finishes, that value is the minimum of the values received from upstream, which
/// is `-1`.
///
/// let numbers = [-1, 0, 10, 5]
/// numbers.publisher
/// .min()
/// .sink { print("\($0)") }
///
/// // Prints: "-1"
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
@@ -22,6 +36,20 @@ extension Publisher where Output: Comparable {
/// Publishes the maximum value received from the upstream publisher, after it
/// finishes.
///
/// Use `max()` to determine the maximum value in the stream of elements from
/// an upstream publisher.
///
/// In the example below, the `max()` operator emits a value when the publisher
/// finishes, that value is the maximum of the values received from upstream, which
/// is `10`.
///
/// let numbers = [0, 10, 5]
/// cancellable = numbers.publisher
/// .max()
/// .sink { print("\($0)") }
///
/// // Prints: "10"
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
@@ -37,11 +65,36 @@ extension Publisher {
/// Publishes the minimum value received from the upstream publisher, after it
/// finishes.
///
/// Use `min(by:)` to determine the minimum value in the stream of elements from
/// an upstream publisher using a comparison operation you specify.
///
/// This operator is useful when the value received from the upstream publisher isnt
/// `Comparable`.
///
/// In the example below an array publishes enumeration elements representing playing
/// card ranks. The `min(by:)` operator compares the current and next elements using
/// the `rawValue` property of each enumeration value in the user supplied closure and
/// prints the minimum value found after publishing all of the elements.
///
/// enum Rank: Int {
/// case ace = 1, two, three, four, five, six, seven, eight, nine,
/// ten, jack, queen, king
/// }
///
/// let cards: [Rank] = [.five, .queen, .ace, .eight, .king]
/// cancellable = cards.publisher
/// .min {
/// return $0.rawValue < $1.rawValue
/// }
/// .sink { print("\($0)") }
///
/// // Prints: "ace"
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
/// `true` if they are in increasing order.
/// `true` if theyre in increasing order.
/// - Returns: A publisher that publishes the minimum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func min(
@@ -50,15 +103,39 @@ extension Publisher {
return max(by: { areInIncreasingOrder($1, $0) })
}
/// Publishes the minimum value received from the upstream publisher, using the
/// provided error-throwing closure to order the items.
/// Publishes the minimum value received from the upstream publisher, using
/// the provided error-throwing closure to order the items.
///
/// Use `tryMin(by:)` to determine the minimum value of elements received from
/// the upstream publisher using an error-throwing closure you specify.
///
/// In the example below, an array publishes elements. The `tryMin(by:)` operator
/// executes the error-throwing closure that throws when the `first` element is an odd
/// number, terminating the publisher.
///
/// struct IllegalValueError: Error {}
///
/// let numbers: [Int] = [0, 10, 6, 13, 22, 22]
/// numbers.publisher
/// .tryMin { first, second -> Bool in
/// if (first % 2 != 0) {
/// throw IllegalValueError()
/// }
/// return first < second
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("value: \($0)") }
/// )
///
/// // Prints: "completion: failure(IllegalValueError())"
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
/// and returns `true` if they are in increasing order. If this closure throws, the
/// publisher terminates with a `Failure`.
/// and returns `true` if theyre in increasing order. If this closure throws,
/// the publisher terminates with a `Subscribers.Completion.failure(_:)`.
/// - Returns: A publisher that publishes the minimum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func tryMin(
@@ -67,14 +144,36 @@ extension Publisher {
return tryMax(by: { try areInIncreasingOrder($1, $0) })
}
/// Publishes the maximum value received from the upstream publisher, using the
/// provided ordering closure.
/// Publishes the maximum value received from the upstream publisher, using
/// the provided ordering closure.
///
/// Use `max(by:)` to determine the maximum value of elements received from
/// the upstream publisher based on an ordering closure you specify.
///
/// In the example below, an array publishes enumeration elements representing playing
/// card ranks. The `max(by:)` operator compares the current and next elements using
/// the `rawValue` property of each enumeration value in the user supplied closure and
/// prints the maximum value found after publishing all of the elements.
///
/// enum Rank: Int {
/// case ace = 1, two, three, four, five, six, seven, eight, nine,
/// ten, jack, queen, king
/// }
///
/// let cards: [Rank] = [.five, .queen, .ace, .eight, .jack]
/// cancellable = cards.publisher
/// .max {
/// return $0.rawValue > $1.rawValue
/// }
/// .sink { print("\($0)") }
///
/// // Prints: "queen"
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
/// `true` if they are in increasing order.
/// `true` if theyre in increasing order.
/// - Returns: A publisher that publishes the maximum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func max(
@@ -83,18 +182,44 @@ extension Publisher {
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
}
/// Publishes the maximum value received from the upstream publisher, using the
/// provided error-throwing closure to order the items.
/// Publishes the maximum value received from the upstream publisher, using
/// the provided error-throwing closure to order the items.
///
/// Use `tryMax(by:)` to determine the maximum value of elements received from
/// the upstream publisher using an error-throwing closure you specify.
///
/// In the example below, an array publishes elements. The `tryMax(by:)` operator
/// executes the error-throwing closure that throws when the `first` element is
/// an odd number, terminating the publisher.
///
/// struct IllegalValueError: Error {}
///
/// let numbers: [Int] = [0, 10, 6, 13, 22, 22]
/// cancellable = numbers.publisher
/// .tryMax { first, second -> Bool in
/// if (first % 2 != 0) {
/// throw IllegalValueError()
/// }
/// return first > second
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("value: \($0)") }
/// )
///
/// // Prints: completion: failure(IllegalValueError())
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
/// and returns `true` if they are in increasing order. If this closure throws, the
/// publisher terminates with a `Failure`.
/// and returns `true` if theyre in increasing order. If this closure throws,
/// the publisher terminates with a ``Subscribers/Completion/failure(_:)``.
///
/// - Returns: A publisher that publishes the maximum value received from the upstream
/// publisher, after the upstream publisher finishes.
/// publisher, after the upstream publisher finishes.
public func tryMax(
by areInIncreasingOrder: @escaping (Self.Output, Self.Output) throws -> Bool
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
) -> Publishers.TryComparison<Self> {
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
}
@@ -0,0 +1,464 @@
//
// Publishers.Concatenate.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
extension Publisher {
/// Prefixes a publishers output with the specified values.
///
/// Use `prepend(_:)` when you need to prepend specific elements before the output
/// of a publisher.
///
/// In the example below, the `prepend(_:)` operator publishes the provided elements
/// before republishing all elements from `dataElements`:
///
/// let dataElements = (0...10)
/// cancellable = dataElements.publisher
/// .prepend(0, 1, 255)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
///
/// - Parameter elements: The elements to publish before this publishers elements.
/// - Returns: A publisher that prefixes the specified elements prior to this
/// publishers elements.
public func prepend(
_ elements: Output...
) -> Publishers.Concatenate<Publishers.Sequence<[Output], Failure>, Self> {
return prepend(elements)
}
/// Prefixes a publishers output with the specified sequence.
///
/// Use `prepend(_:)` to publish values from two publishers when you need to prepend
/// one publishers elements to another.
///
/// In this example the `/prepend(_:)-v9sb` operator publishes the provided sequence
/// before republishing all elements from `dataElements`:
///
/// let prefixValues = [0, 1, 255]
/// let dataElements = (0...10)
/// cancellable = dataElements.publisher
/// .prepend(prefixValues)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
///
/// - Parameter elements: A sequence of elements to publish before this publishers
/// elements.
/// - Returns: A publisher that prefixes the sequence of elements prior to this
/// publishers elements.
public func prepend<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Concatenate<Publishers.Sequence<Elements, Failure>, Self>
where Output == Elements.Element
{
return prepend(.init(sequence: elements))
}
/// Prefixes the output of this publisher with the elements emitted by the given
/// publisher.
///
/// Use `prepend(_:)` to publish values from two publishers when you need to prepend
/// one publishers elements to another.
///
/// In the example below, a publisher of `prefixValues` publishes its elements before
/// the `dataElements` publishes its elements:
///
/// let prefixValues = [0, 1, 255]
/// let dataElements = (0...10)
/// cancellable = dataElements.publisher
/// .prepend(prefixValues.publisher)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1 255 0 1 2 3 4 5 6 7 8 9 10"
///
/// - Parameter publisher: The prefixing publisher.
/// - Returns: A publisher that prefixes the prefixing publishers elements prior to
/// this publishers elements.
public func prepend<Prefix: Publisher>(
_ publisher: Prefix
) -> Publishers.Concatenate<Prefix, Self>
where Failure == Prefix.Failure, Output == Prefix.Output
{
return .init(prefix: publisher, suffix: self)
}
/// Appends a publishers output with the specified elements.
///
/// Use `append(_:)` when you need to prepend specific elements after the output of
/// a publisher.
///
/// In the example below, the `append(_:)` operator publishes the provided elements
/// after republishing all elements from `dataElements`:
///
/// let dataElements = (0...10)
/// cancellable = dataElements.publisher
/// .append(0, 1, 255)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1 2 3 4 5 6 7 8 9 10 0 1 255"
///
///
/// - Parameter elements: Elements to publish after this publishers elements.
/// - Returns: A publisher that appends the specifiecd elements after this publishers
/// elements.
public func append(
_ elements: Output...
) -> Publishers.Concatenate<Self, Publishers.Sequence<[Output], Failure>> {
return append(elements)
}
/// Appends a publishers output with the specified sequence.
///
/// Use `append(_:)` to append a sequence to the end of
/// a publishers output.
///
/// In the example below, the `append(_:)` publisher republishes all elements from
/// `groundTransport` until it finishes, then publishes the members of `airTransport`:
///
/// let groundTransport = ["car", "bus", "truck", "subway", "bicycle"]
/// let airTransport = ["parasail", "jet", "helicopter", "rocket"]
/// cancellable = groundTransport.publisher
/// .append(airTransport)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "car bus truck subway bicycle parasail jet helicopter rocket"
///
/// - Parameter elements: A sequence of elements to publish after this publishers
/// elements.
/// - Returns: A publisher that appends the sequence of elements after this
/// publishers elements.
public func append<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Concatenate<Self, Publishers.Sequence<Elements, Failure>>
where Output == Elements.Element
{
return append(.init(sequence: elements))
}
/// Appends the output of this publisher with the elements emitted by the given
/// publisher.
///
/// Use `append(_:)` to append the output of one publisher to another.
/// The `append(_:)` operator produces no elements until this publisher finishes.
/// It then produces this publishers elements, followed by the given publishers
/// elements. If this publisher fails with an error, the given publishers elements
/// arent published.
///
/// In the example below, the `append` publisher republishes all elements from
/// the `numbers` publisher until it finishes, then publishes all elements from
/// the `otherNumbers` publisher:
///
/// let numbers = (0...10)
/// let otherNumbers = (25...35)
/// cancellable = numbers.publisher
/// .append(otherNumbers.publisher)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1 2 3 4 5 6 7 8 9 10 25 26 27 28 29 30 31 32 33 34 35 "
///
/// - Parameter publisher: The appending publisher.
/// - Returns: A publisher that appends the appending publishers elements after this
/// publishers elements.
public func append<Suffix: Publisher>(
_ publisher: Suffix
) -> Publishers.Concatenate<Self, Suffix>
where Suffix.Failure == Failure, Suffix.Output == Output
{
return .init(prefix: self, suffix: publisher)
}
}
extension Publishers {
/// A publisher that emits all of one publishers elements before those from another
/// publisher.
public struct Concatenate<Prefix: Publisher, Suffix: Publisher>: Publisher
where Prefix.Failure == Suffix.Failure, Prefix.Output == Suffix.Output
{
public typealias Output = Suffix.Output
public typealias Failure = Suffix.Failure
/// The publisher to republish, in its entirety, before republishing elements from
/// `suffix`.
public let prefix: Prefix
/// The publisher to republish only after `prefix` finishes.
public let suffix: Suffix
public init(prefix: Prefix, suffix: Suffix) {
self.prefix = prefix
self.suffix = suffix
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Suffix.Failure == Downstream.Failure, Suffix.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, suffix: suffix)
prefix.subscribe(Inner<Downstream>.PrefixSubscriber(inner: inner))
}
}
}
extension Publishers.Concatenate: Equatable where Prefix: Equatable, Suffix: Equatable {}
extension Publishers.Concatenate {
fileprivate final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Suffix.Output, Downstream.Failure == Suffix.Failure
{
typealias Input = Suffix.Output
typealias Failure = Suffix.Failure
fileprivate struct PrefixSubscriber {
let inner: Inner<Downstream>
}
fileprivate struct SuffixSubscriber {
let inner: Inner<Downstream>
}
private let downstream: Downstream
private var prefixState = SubscriptionStatus.awaitingSubscription
private var suffixState = SubscriptionStatus.awaitingSubscription
private let suffix: Suffix
private var pending = Subscribers.Demand.none
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream, suffix: Suffix) {
self.downstream = downstream
self.suffix = suffix
}
deinit {
lock.deallocate()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
pending += demand
guard let subscription = prefixState.subscription ?? suffixState.subscription
else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
let upstreamSubscription =
prefixState.subscription ?? suffixState.subscription
prefixState = .terminal
suffixState = .terminal
lock.unlock()
upstreamSubscription?.cancel()
}
var description: String { return "Concatenate" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
// MARK: - Private
private func prefixReceive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = prefixState else {
lock.unlock()
subscription.cancel()
return
}
prefixState = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
private func prefixReceive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = prefixState, pending != .none else {
lock.unlock()
return .none
}
pending -= 1
lock.unlock()
let newDemand = downstream.receive(input)
if newDemand == .none {
return .none
}
lock.lock()
pending += newDemand
lock.unlock()
return newDemand
}
private func prefixReceive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .subscribed = prefixState else {
lock.unlock()
return
}
prefixState = .terminal
lock.unlock()
switch completion {
case .finished:
suffix.subscribe(SuffixSubscriber(inner: self))
case .failure:
downstream.receive(completion: completion)
}
}
private func suffixReceive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = suffixState else {
lock.unlock()
subscription.cancel()
return
}
suffixState = .subscribed(subscription)
let pending = self.pending
lock.unlock()
if pending != .none {
subscription.request(pending)
}
}
private func suffixReceive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = suffixState else {
lock.unlock()
return .none
}
lock.unlock()
return downstream.receive(input)
}
private func suffixReceive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .subscribed = suffixState else {
lock.unlock()
return
}
prefixState = .terminal
suffixState = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
}
}
// MARK: - PrefixSubscriber conformances
extension Publishers.Concatenate.Inner.PrefixSubscriber: Subscriber {
fileprivate typealias Input = Suffix.Output
fileprivate typealias Failure = Suffix.Failure
fileprivate var combineIdentifier: CombineIdentifier {
return inner.combineIdentifier
}
fileprivate func receive(subscription: Subscription) {
inner.prefixReceive(subscription: subscription)
}
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
return inner.prefixReceive(input)
}
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
inner.prefixReceive(completion: completion)
}
}
extension Publishers.Concatenate.Inner.PrefixSubscriber
: CustomStringConvertible
{
fileprivate var description: String {
return inner.description
}
}
extension Publishers.Concatenate.Inner.PrefixSubscriber
: CustomReflectable
{
fileprivate var customMirror: Mirror {
return inner.customMirror
}
}
extension Publishers.Concatenate.Inner.PrefixSubscriber
: CustomPlaygroundDisplayConvertible
{
fileprivate var playgroundDescription: Any {
return inner.playgroundDescription
}
}
// MARK: - SuffixSubscriber conformances
extension Publishers.Concatenate.Inner.SuffixSubscriber: Subscriber {
fileprivate typealias Input = Suffix.Output
fileprivate typealias Failure = Suffix.Failure
fileprivate var combineIdentifier: CombineIdentifier {
return inner.combineIdentifier
}
fileprivate func receive(subscription: Subscription) {
inner.suffixReceive(subscription: subscription)
}
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
return inner.suffixReceive(input)
}
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
inner.suffixReceive(completion: completion)
}
}
extension Publishers.Concatenate.Inner.SuffixSubscriber
: CustomStringConvertible
{
fileprivate var description: String {
return inner.description
}
}
extension Publishers.Concatenate.Inner.SuffixSubscriber
: CustomReflectable
{
fileprivate var customMirror: Mirror {
return inner.customMirror
}
}
extension Publishers.Concatenate.Inner.SuffixSubscriber
: CustomPlaygroundDisplayConvertible
{
fileprivate var playgroundDescription: Any {
return inner.playgroundDescription
}
}
@@ -9,10 +9,22 @@ extension Publisher where Output: Equatable {
/// Publishes a Boolean value upon receiving an element equal to the argument.
///
/// The contains publisher consumes all received elements until the upstream publisher
/// produces a matching element. At that point, it emits `true` and finishes normally.
/// If the upstream finishes normally without producing a matching element,
/// this publisher emits `false`, then finishes.
/// Use `contains(_:)` to find the first element in an upstream thats equal to
/// the supplied argument. The `Publishers.Contains` publisher consumes all received
/// elements until the upstream publisher produces a matching element. Upon finding
/// the first match, it emits `true` and finishes normally. If the upstream finishes
/// normally without producing a matching element, this publisher emits `false` and
/// finishes.
///
/// In the example below, the `contains(_:)` operator emits `true` the first time it
/// receives the value `5` from the `numbers.publisher`, and then finishes normally.
///
/// let numbers = [-1, 5, 10, 5]
/// numbers.publisher
/// .contains(5)
/// .sink { print("\($0)") }
///
/// // Prints: "true"
///
/// - Parameter output: An element to match against.
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
@@ -27,12 +39,27 @@ extension Publisher {
/// Publishes a Boolean value upon receiving an element that satisfies the predicate
/// closure.
///
/// This operator consumes elements produced from the upstream publisher until
/// the upstream publisher produces a matching element.
/// Use `contains(where:)` to find the first element in an upstream that satisfies
/// the closure you provide. This operator consumes elements produced from
/// the upstream publisher until the upstream publisher produces a matching element.
///
/// This operator is useful when the upstream publisher produces elements that dont
/// conform to `Equatable`.
///
/// In the example below, the `contains(where:)` operator tests elements against
/// the supplied closure and emits `true` for the first elements thats greater than
/// `4`, and then finishes normally.
///
/// let numbers = [-1, 0, 10, 5]
/// numbers.publisher
/// .contains {$0 > 4}
/// .sink { print("\($0)") }
///
/// // Prints: "true"
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether the element satisfies the closures
/// comparison logic.
/// returns a Boolean value that indicates whether the element satisfies
/// the closures comparison logic.
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
/// publisher emits a matching value.
public func contains(
@@ -41,16 +68,47 @@ extension Publisher {
return .init(upstream: self, predicate: predicate)
}
/// Publishes a Boolean value upon receiving an element that satisfies
/// the throwing predicate closure.
/// Publishes a Boolean value upon receiving an element that satisfies the throwing
/// predicate closure.
///
/// Use `tryContains(where:)` to find the first element in an upstream that satisfies
/// the error-throwing closure you provide.
///
/// This operator consumes elements produced from the upstream publisher until
/// the upstream publisher produces a matching element. If the closure throws,
/// the stream fails with an error.
/// the upstream publisher either:
///
/// - Produces a matching element, after which it emits `true` and the publisher
/// finishes normally.
/// - Emits `false` if no matching element is found and the publisher finishes
/// normally.
///
/// If the predicate throws an error, the publisher fails, passing the error to its
/// downstream.
///
/// In the example below, the `tryContains(where:)` operator tests values to find
/// an element less than `10`; when the closure finds an odd number, like `3`,
/// the publisher terminates with an `IllegalValueError`.
///
/// struct IllegalValueError: Error {}
///
/// let numbers = [3, 2, 10, 5, 0, 9]
/// numbers.publisher
/// .tryContains {
/// if ($0 % 2 != 0) {
/// throw IllegalValueError()
/// }
/// return $0 < 10
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("value: \($0)") }
/// )
///
/// // Prints: "completion: failure(IllegalValueError())"
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether the element satisfies the closures
/// comparison logic.
/// returns a Boolean value that indicates whether the element satisfies
/// the closures comparison logic.
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
/// publisher emits a matching value.
public func tryContains(
@@ -9,8 +9,18 @@ extension Publisher {
/// Publishes the number of elements received from the upstream publisher.
///
/// Use `count(`` to determine the number of elements received from the upstream
/// publisher before it completes:
///
/// let numbers = (0...10)
/// cancellable = numbers.publisher
/// .count()
/// .sink { print("\($0)") }
///
/// // Prints: "11"
///
/// - Returns: A publisher that consumes all elements until the upstream publisher
/// finishes, then emits a single value with the total number of elements received.
/// finishes, then emits a single value with the total number of elements received.
public func count() -> Publishers.Count<Self> {
return Publishers.Count(upstream: self)
}
@@ -0,0 +1,336 @@
//
// Publishers.Debounce.swift
//
//
// Created by Sergej Jaskiewicz on 17.12.2019.
//
extension Publisher {
/// Publishes elements only after a specified time interval elapses between events.
///
/// Use the `debounce(for:scheduler:options:)` operator to control the number of
/// values and time between delivery of values from the upstream publisher. This
/// operator is useful to process bursty or high-volume event streams where you need
/// to reduce the number of values delivered to the downstream to a rate you specify.
///
/// In this example, a `PassthroughSubject` publishes elements on a schedule defined
/// by the `bounces` array. The array is composed of tuples representing a value sent
/// by the `PassthroughSubject`, and a `TimeInterval` ranging from one-quarter second
/// up to 2 seconds that drives a delivery timer. As the queue builds, elements
/// arriving faster than one-half second `debounceInterval` are discarded, while
/// elements arriving at a rate slower than `debounceInterval` are passed through to
/// the `sink(receiveValue:)` operator.
///
/// let bounces:[(Int,TimeInterval)] = [
/// (0, 0),
/// (1, 0.25), // 0.25s interval since last index
/// (2, 1), // 0.75s interval since last index
/// (3, 1.25), // 0.25s interval since last index
/// (4, 1.5), // 0.25s interval since last index
/// (5, 2) // 0.5s interval since last index
/// ]
///
/// let subject = PassthroughSubject<Int, Never>()
/// cancellable = subject
/// .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
/// .sink { index in
/// print ("Received index \(index)")
/// }
///
/// for bounce in bounces {
/// DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
/// subject.send(bounce.0)
/// }
/// }
///
/// // Prints:
/// // Received index 1
/// // Received index 4
/// // Received index 5
///
/// // Here is the event flow shown from the perspective of time, showing value
/// // delivery through the `debounce()` operator:
///
/// // Time 0: Send index 0.
/// // Time 0.25: Send index 1. Index 0 was waiting and is discarded.
/// // Time 0.75: Debounce period ends, publish index 1.
/// // Time 1: Send index 2.
/// // Time 1.25: Send index 3. Index 2 was waiting and is discarded.
/// // Time 1.5: Send index 4. Index 3 was waiting and is discarded.
/// // Time 2: Debounce period ends, publish index 4. Also, send index 5.
/// // Time 2.5: Debounce period ends, publish index 5.
///
/// - Parameters:
/// - dueTime: The time the publisher should wait before publishing an element.
/// - scheduler: The scheduler on which this publisher delivers elements
/// - options: Scheduler options that customize this publishers delivery
/// of elements.
/// - Returns: A publisher that publishes events only after a specified time elapses.
public func debounce<Context: Scheduler>(
for dueTime: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.Debounce<Self, Context> {
return .init(upstream: self,
dueTime: dueTime,
scheduler: scheduler,
options: options)
}
}
extension Publishers {
/// A publisher that publishes elements only after a specified time interval elapses
/// between events.
public struct Debounce<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The amount of time the publisher should wait before publishing an element.
public let dueTime: Context.SchedulerTimeType.Stride
/// The scheduler on which this publisher delivers elements.
public let scheduler: Context
/// Scheduler options that customize this publishers delivery of elements.
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
dueTime: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.upstream = upstream
self.dueTime = dueTime
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
let inner = Inner(downstream: subscriber,
dueTime: dueTime,
scheduler: scheduler,
options: options)
upstream.subscribe(inner)
}
}
}
extension Publishers.Debounce {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private typealias Generation = UInt64
private enum CancellerState {
case pending
case active(Cancellable)
fileprivate func cancel() {
if case let .active(cancellable) = self {
cancellable.cancel()
}
}
}
private let lock = UnfairLock.allocate()
private let downstreamLock = UnfairRecursiveLock.allocate()
private let downstream: Downstream
private let dueTime: Context.SchedulerTimeType.Stride
private let scheduler: Context
private let options: Context.SchedulerOptions?
private var state = SubscriptionStatus.awaitingSubscription
private var currentCancellers = [Generation : CancellerState]()
private var currentValue: Output?
private var currentGeneration: Generation = 0
private var downstreamDemand = Subscribers.Demand.none
init(downstream: Downstream,
dueTime: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.downstream = downstream
self.dueTime = dueTime
self.scheduler = scheduler
self.options = options
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscription)
lock.unlock()
downstreamLock.lock()
downstream.receive(subscription: self)
downstreamLock.unlock()
subscription.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return .none
}
currentGeneration += 1
let generation = currentGeneration
currentValue = input
let due = scheduler.now.advanced(by: dueTime)
let previousCancellers = self.currentCancellers
currentCancellers.removeAll()
currentCancellers[generation] = .pending
lock.unlock()
let newCanceller = scheduler.schedule(after: due,
interval: dueTime,
tolerance: scheduler.minimumTolerance,
options: options) {
self.due(generation: generation)
}
lock.lock()
currentCancellers[generation] = .active(newCanceller)
lock.unlock()
for canceller in previousCancellers.values {
canceller.cancel()
}
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return
}
state = .terminal
let previousCancellers = currentCancellers
currentCancellers.removeAll()
lock.unlock()
for canceller in previousCancellers.values {
canceller.cancel()
}
scheduler.schedule {
self.downstreamLock.lock()
self.downstream.receive(completion: completion)
self.downstreamLock.unlock()
}
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return
}
downstreamDemand += demand
lock.unlock()
}
func cancel() {
lock.lock()
guard case .subscribed(let subscription) = state else {
lock.unlock()
return
}
state = .terminal
let previousCancellers = currentCancellers
currentCancellers.removeAll()
lock.unlock()
for canceller in previousCancellers.values {
canceller.cancel()
}
subscription.cancel()
}
var description: String { return "Debounce" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("downstreamDemand", downstreamDemand),
("currentValue", currentValue as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
private func due(generation: Generation) {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return
}
// If this condition holds, it means that no values were received
// in this time frame => we should propagate the current value downstream.
guard generation == currentGeneration, let value = currentValue else {
let canceller = currentCancellers[generation]
lock.unlock()
canceller?.cancel()
return
}
guard let canceller = currentCancellers[generation] else {
lock.unlock()
return
}
currentCancellers[generation] = nil
let hasAnyDemand = downstreamDemand != .none
if hasAnyDemand {
downstreamDemand -= 1
}
lock.unlock()
canceller.cancel()
guard hasAnyDemand else { return }
downstreamLock.lock()
let newDemand = downstream.receive(value)
downstreamLock.unlock()
if newDemand == .none { return }
lock.lock()
downstreamDemand += newDemand
lock.unlock()
}
}
}
@@ -1,109 +0,0 @@
//
// Publishers.Decode.swift
//
//
// Created by Joseph Spadafora on 6/21/19.
//
extension Publishers {
public struct Decode<Upstream, Output, Coder>: Publisher
where Upstream: Publisher,
Output: Decodable,
Coder: TopLevelDecoder,
Upstream.Output == Coder.Input
{
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
public let upstream: Upstream
private let _decoder: Coder
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decoder = decoder
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
let decodeSubscriber = _Decode<Upstream, Downstream, Coder>(
downstream: subscriber,
decoder: _decoder
)
upstream.subscribe(decodeSubscriber)
}
}
}
private final class _Decode<Upstream: Publisher,
Downstream: Subscriber,
Coder: TopLevelDecoder>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Downstream.Input: Decodable,
Coder.Input == Upstream.Output,
Downstream.Failure == Error {
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
private let _decoder: Coder
var description: String { return "Decode" }
init(downstream: Downstream, decoder: Coder) {
self._decoder = decoder
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
let value = try _decoder.decode(Downstream.Input.self, from: input)
return downstream.receive(value)
} catch {
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
}
extension Publisher {
public func decode<Item: Decodable,
Coder: TopLevelDecoder>(
type: Item.Type,
decoder: Coder
) -> Publishers.Decode<Self, Item, Coder>
where Self.Output == Coder.Input
{
return Publishers.Decode(upstream: self, decoder: decoder)
}
}
@@ -0,0 +1,269 @@
//
// Publishers.Delay.swift
// OpenCombine
//
// Created by Евгений Богомолов on 07/09/2019.
//
extension Publisher {
/// Delays delivery of all output to the downstream receiver by a specified amount of
/// time on a particular scheduler.
///
/// Use `delay(for:tolerance:scheduler:options:)` when you need to delay the delivery
/// of elements to a downstream by a specified amount of time.
///
/// In this example, a `Timer` publishes an event every second.
/// The `delay(for:tolerance:scheduler:options:)` operator holds the delivery of
/// the initial element for 3 seconds (±0.5 seconds), after which each element is
/// delivered to the downstream on the main run loop after the specified delay:
///
/// let df = DateFormatter()
/// df.dateStyle = .none
/// df.timeStyle = .long
/// cancellable = Timer.publish(every: 1.0, on: .main, in: .default)
/// .autoconnect()
/// .handleEvents(receiveOutput: { date in
/// print ("Sending Timestamp \'\(df.string(from: date))\' to delay()")
/// })
/// .delay(for: .seconds(3), scheduler: RunLoop.main, options: .none)
/// .sink(
/// receiveCompletion: { print ("completion: \($0)", terminator: "\n") },
/// receiveValue: { value in
/// let now = Date()
/// print("""
/// At \(df.string(from: now)) received Timestamp \
/// \'\(df.string(from: value))\' \
/// sent: \(String(format: "%.2f", now.timeIntervalSince(value)))
/// secs ago
/// """)
/// }
/// )
///
/// // Prints:
/// // Sending Timestamp '5:02:33 PM PDT' to delay()
/// // Sending Timestamp '5:02:34 PM PDT' to delay()
/// // Sending Timestamp '5:02:35 PM PDT' to delay()
/// // Sending Timestamp '5:02:36 PM PDT' to delay()
/// // At 5:02:36 PM PDT received Timestamp '5:02:33 PM PDT' sent: 3.00
/// // secs ago
/// // Sending Timestamp '5:02:37 PM PDT' to delay()
/// // At 5:02:37 PM PDT received Timestamp '5:02:34 PM PDT' sent: 3.00
/// // secs ago
/// // Sending Timestamp '5:02:38 PM PDT' to delay()
/// // At 5:02:38 PM PDT received Timestamp '5:02:35 PM PDT' sent: 3.00
/// // secs ago
///
/// The delay affects the delivery of elements and completion, but not of the original
/// subscription.
///
/// - Parameters:
/// - interval: The amount of time to delay.
/// - tolerance: The allowed tolerance in firing delayed events.
/// - scheduler: The scheduler to deliver the delayed events.
/// - options: Options relevant to the schedulers behavior.
/// - Returns: A publisher that delays delivery of elements and completion to
/// the downstream receiver.
public func delay<Context: Scheduler>(
for interval: Context.SchedulerTimeType.Stride,
tolerance: Context.SchedulerTimeType.Stride? = nil,
scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.Delay<Self, Context> {
return .init(upstream: self,
interval: interval,
tolerance: tolerance ?? scheduler.minimumTolerance,
scheduler: scheduler,
options: options)
}
}
extension Publishers {
/// A publisher that delays delivery of elements and completion
/// to the downstream receiver.
public struct Delay<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// The amount of time to delay.
public let interval: Context.SchedulerTimeType.Stride
/// The allowed tolerance in firing delayed events.
public let tolerance: Context.SchedulerTimeType.Stride
/// The scheduler to deliver the delayed events.
public let scheduler: Context
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
interval: Context.SchedulerTimeType.Stride,
tolerance: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions? = nil)
{
self.upstream = upstream
self.interval = interval
self.tolerance = tolerance
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber,
interval: interval,
tolerance: tolerance,
scheduler: scheduler,
options: options)
upstream.subscribe(inner)
}
}
}
extension Publishers.Delay {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let lock = UnfairLock.allocate()
private let downstream: Downstream
private let interval: Context.SchedulerTimeType.Stride
private let tolerance: Context.SchedulerTimeType.Stride
private let scheduler: Context
private let options: Context.SchedulerOptions?
private var state = SubscriptionStatus.awaitingSubscription
private let downstreamLock = UnfairRecursiveLock.allocate()
fileprivate init(downstream: Downstream,
interval: Context.SchedulerTimeType.Stride,
tolerance: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.downstream = downstream
self.interval = interval
self.tolerance = tolerance
self.scheduler = scheduler
self.options = options
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
private func schedule(_ work: @escaping () -> Void) {
scheduler
.schedule(after: scheduler.now.advanced(by: interval),
tolerance: tolerance,
options: options,
work)
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscription)
lock.unlock()
downstreamLock.lock()
downstream.receive(subscription: self)
downstreamLock.unlock()
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return .none
}
lock.unlock()
schedule {
self.scheduledReceive(input)
}
return .none
}
private func scheduledReceive(_ input: Input) {
lock.lock()
guard state.subscription != nil else {
lock.unlock()
return
}
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
if newDemand == .none { return }
lock.lock()
let subscription = state.subscription
lock.unlock()
subscription?.request(newDemand)
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
state = .pendingTerminal(subscription)
lock.unlock()
schedule {
self.scheduledReceive(completion: completion)
}
}
private func scheduledReceive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .pendingTerminal = state else {
assertionFailure(
"This branch should not be reachable! Please report a bug."
)
lock.unlock()
return
}
state = .terminal
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subscription.cancel()
}
}
}
@@ -5,13 +5,23 @@
// 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.
/// Use `dropFirst(_:)` when you want to drop the first `n` elements from the upstream
/// publisher, and republish the remaining elements.
///
/// The example below drops the first five elements from the stream:
///
/// let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
/// cancellable = numbers.publisher
/// .dropFirst(5)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "6 7 8 9 10 "
///
/// - Parameter count: The number of elements to omit. The default is `1`.
/// - Returns: A publisher that doesnt republish the first `count` elements.
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self> {
return .init(upstream: self, count: count)
}
@@ -42,8 +52,8 @@ extension Publishers {
Upstream.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, count: count)
upstream.subscribe(inner)
subscriber.receive(subscription: inner)
upstream.subscribe(inner)
}
}
}
@@ -60,8 +70,6 @@ extension Publishers.Drop {
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
// NOTE: This class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
@@ -96,7 +104,7 @@ extension Publishers.Drop {
precondition(count >= 0, "count must not be negative")
let demandToRequestFromUpstream = pendingDemand + count
lock.unlock()
if demandToRequestFromUpstream > 0 {
if demandToRequestFromUpstream != .none {
subscription.request(demandToRequestFromUpstream)
}
}
@@ -111,8 +119,9 @@ extension Publishers.Drop {
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
// Combine doesn't lock here!
lock.lock()
subscription = nil
lock.unlock()
downstream.receive(completion: completion)
}
@@ -129,9 +138,11 @@ extension Publishers.Drop {
}
func cancel() {
// Combine doesn't lock here!
lock.lock()
let subscription = self.subscription
self.subscription = nil
lock.unlock()
subscription?.cancel()
subscription = nil
}
var description: String { return "Drop" }
@@ -0,0 +1,286 @@
//
// Publishers.DropUntilOutput.swift
//
//
// Created by Sergej Jaskiewicz on 24.12.2019.
//
extension Publisher {
/// Ignores elements from the upstream publisher until it receives an element from
/// a second publisher.
///
/// Use `drop(untilOutputFrom:)` to ignore elements from the upstream publisher until
/// another, second, publisher delivers its first element.
/// This publisher requests a single value from the second publisher, and it ignores
/// (drops) all elements from the upstream publisher until the second publisher
/// produces a value. After the second publisher produces an element,
/// `drop(untilOutputFrom:)` cancels its subscription to the second publisher, and
/// allows events from the upstream publisher to pass through.
///
/// After this publisher receives a subscription from the upstream publisher, it
/// passes through backpressure requests from downstream to the upstream publisher.
/// If the upstream publisher acts on those requests before the other publisher
/// produces an item, this publisher drops the elements it receives from the upstream
/// publisher.
///
/// In the example below, the `pub1` publisher defers publishing its elements until
/// the `pub2` publisher delivers its first element:
///
/// let upstream = PassthroughSubject<Int, Never>()
/// let second = PassthroughSubject<String, Never>()
/// cancellable = upstream
/// .drop(untilOutputFrom: second)
/// .sink { print("\($0)", terminator: " ") }
///
/// upstream.send(1)
/// upstream.send(2)
/// second.send("A")
/// upstream.send(3)
/// upstream.send(4)
/// // Prints "3 4"
///
/// - Parameter publisher: A publisher to monitor for its first emitted element.
/// - Returns: A publisher that drops elements from the upstream publisher until
/// the `other` publisher produces a value.
public func drop<Other: Publisher>(
untilOutputFrom publisher: Other
) -> Publishers.DropUntilOutput<Self, Other> where Failure == Other.Failure {
return .init(upstream: self, other: publisher)
}
}
extension Publishers {
/// A publisher that ignores elements from the upstream publisher until it receives
/// an element from second publisher.
public struct DropUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher
where Upstream.Failure == Other.Failure
{
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A publisher to monitor for its first emitted element.
public let other: Other
/// Creates a publisher that ignores elements from the upstream publisher until
/// it receives an element from another publisher.
///
/// - Parameters:
/// - upstream: A publisher to drop elements from while waiting for another
/// publisher to emit elements.
/// - other: A publisher to monitor for its first emitted element.
public init(upstream: Upstream, other: Other) {
self.upstream = upstream
self.other = other
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input,
Other.Failure == Downstream.Failure
{
let inner = Inner(downstream: subscriber)
subscriber.receive(subscription: inner)
other.subscribe(Inner.OtherSubscriber(inner: inner))
upstream.subscribe(inner)
}
}
}
extension Publishers.DropUntilOutput: Equatable
where Upstream: Equatable, Other: Equatable {}
extension Publishers.DropUntilOutput {
fileprivate final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private var triggered = false
private let lock = UnfairLock.allocate()
private let downstreamLock = UnfairRecursiveLock.allocate()
private var upstreamSubscription: Subscription?
private var pendingDemand = Subscribers.Demand.none
private var otherSubscription: Subscription?
private var otherFinished = false
private var cancelled = false
init(downstream: Downstream) {
self.downstream = downstream
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard upstreamSubscription == nil && !cancelled else {
lock.unlock()
subscription.cancel()
return
}
upstreamSubscription = subscription
if pendingDemand > 0 {
lock.unlock()
subscription.request(pendingDemand)
} else {
lock.unlock()
}
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
if !triggered || cancelled {
pendingDemand -= 1
lock.unlock()
return .none
}
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
return newDemand
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
if cancelled {
lock.unlock()
return
}
cancelled = true
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
private func receiveOther(subscription: Subscription) {
// Combine doesn't lock here
guard otherSubscription == nil else {
subscription.cancel()
return
}
otherSubscription = subscription
subscription.request(.max(1))
}
private func receiveOther(_ input: Other.Output) -> Subscribers.Demand {
lock.lock()
triggered = true
otherSubscription = nil
lock.unlock()
return .none
}
private func receiveOther(completion: Subscribers.Completion<Other.Failure>) {
lock.lock()
if triggered {
otherSubscription = nil
lock.unlock()
return
}
otherFinished = true
if let upstreamSubscription = self.upstreamSubscription {
self.upstreamSubscription = nil
lock.unlock()
upstreamSubscription.cancel()
} else {
lock.unlock()
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
pendingDemand += demand
if let subscription = upstreamSubscription {
lock.unlock()
subscription.request(demand)
} else {
lock.unlock()
}
}
func cancel() {
lock.lock()
let upstreamSubscription = self.upstreamSubscription
let otherSubscription = self.otherSubscription
self.upstreamSubscription = nil
self.otherSubscription = nil
cancelled = true
lock.unlock()
upstreamSubscription?.cancel()
otherSubscription?.cancel()
}
var description: String { return "DropUntilOutput" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.DropUntilOutput.Inner {
fileprivate struct OtherSubscriber
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
let inner: Publishers.DropUntilOutput<Upstream, Other>.Inner<Downstream>
var combineIdentifier: CombineIdentifier {
return inner.combineIdentifier
}
func receive(subscription: Subscription) {
inner.receiveOther(subscription: subscription)
}
func receive(_ input: Other.Output) -> Subscribers.Demand {
return inner.receiveOther(input)
}
func receive(completion: Subscribers.Completion<Other.Failure>) {
inner.receiveOther(completion: completion)
}
var description: String { return "DropUntilOutput" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -5,6 +5,81 @@
// Created by Sergej Jaskiewicz on 16.06.2019.
//
extension Publisher {
/// Omits elements from the upstream publisher until a given closure returns false,
/// before republishing all remaining elements.
///
/// Use `drop(while:)` to omit elements from an upstream publisher until the element
/// received meets a condition you specify.
///
/// In the example below, the operator omits all elements in the stream until
/// the first element arrives thats a positive integer, after which the operator
/// publishes all remaining elements:
///
/// let numbers = [-62, -1, 0, 10, 0, 22, 41, -1, 5]
/// cancellable = numbers.publisher
/// .drop { $0 <= 0 }
/// .sink { print("\($0)") }
///
/// // Prints: "10 0 22 41 -1 5"
///
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the publishers
/// output.
/// - Returns: A publisher that skips over elements until the provided closure returns
/// `false`.
public func drop(
while predicate: @escaping (Output) -> Bool
) -> Publishers.DropWhile<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Omits elements from the upstream publisher until an error-throwing closure returns
/// false, before republishing all remaining elements.
///
/// Use `Publisher/tryDrop(while:)` to omit elements from an upstream until
/// an error-throwing closure you provide returns false, after which the remaining
/// items in the stream are published. If the closure throws, no elements are emitted
/// and the publisher fails with an error.
///
/// In the example below, elements are ignored until `-1` is encountered in the stream
/// and the closure returns `false`. The publisher then republishes the remaining
/// elements and finishes normally. Conversely, if the `guard` value in the closure
/// had been encountered, the closure would throw and the publisher would fail with
/// an error.
///
/// struct RangeError: Error {}
/// var numbers = [1, 2, 3, 4, 5, 6, -1, 7, 8, 9, 10]
/// let range: CountableClosedRange<Int> = (1...100)
/// cancellable = numbers.publisher
/// .tryDrop {
/// guard $0 != 0 else { throw RangeError() }
/// return range.contains($0)
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("value: \($0)") }
/// )
///
/// // Prints: "-1 7 8 9 10 completion: finished"
/// // If instead numbers was [1, 2, 3, 4, 5, 6, 0, -1, 7, 8, 9, 10],
/// // tryDrop(while:) would fail with a RangeError.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the publishers
/// output.
/// - Returns: A publisher that skips over elements until the provided closure returns
/// `false`, and then republishes all remaining elements. If the predicate closure
/// throws, the publisher fails with an error.
public func tryDrop(
while predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryDropWhile<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that omits elements from an upstream publisher until a given closure
@@ -29,8 +104,7 @@ extension Publishers {
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.subscribe(inner)
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
@@ -56,148 +130,249 @@ extension Publishers {
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Error
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.subscribe(inner)
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscription
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Predicate = (Input) -> Result<Bool, Downstream.Failure>
/// The predicate is reset to `nil` as soon as it returns `false`.
var predicate: Predicate?
var isCompleted = false
init(downstream: Downstream, predicate: @escaping Predicate) {
self.predicate = predicate
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard upstreamSubscription != nil else {
return .none
}
guard let predicate = self.predicate else {
return downstream.receive(input)
}
// NOTE: until the predicate returns false, we will ask the upstream publisher
// for elements one by one.
//
// However, IF the downstream requests anything, we accumulate this demand in the
// `demand` property so that later we can provide the downstream with the correct
// amount of values.
//
// As soon as the predicate returns false, we switch to the mode where
// we just forward all the requests from the downstream to the upstream.
switch predicate(input) {
case .success(true):
return .max(1)
case .success(false):
// Okay, we hit the first element not satisfying the predicate,
// from now on we just republish the values to the downstream.
self.predicate = nil
return downstream.receive(input)
case .failure(let error):
downstream.receive(completion: .failure(error))
isCompleted = true
cancel()
return .none
}
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
override func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
isCompleted = true
// Don't zero out downstream, that's what Combine does (probably a bug)
}
}
extension Publishers.DropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
: Subscriber,
Subscription,
CustomStringConvertible,
Subscriber
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
var description: String { return "DropWhile" }
// NOTE: This class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private var status = SubscriptionStatus.awaitingSubscription
private let downstream: Downstream
private var predicate: ((Input) -> Bool)?
private var dropping = true
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream, predicate: @escaping (Input) -> Bool) {
self.downstream = downstream
self.predicate = predicate
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = status, let shouldDrop = predicate else {
lock.unlock()
return .none
}
let dropping = self.dropping
lock.unlock()
if dropping {
if shouldDrop(input) {
return .max(1)
} else {
lock.lock()
self.dropping = false
lock.unlock()
}
}
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
predicate = nil
lock.unlock()
downstream.receive(completion: completion)
isCompleted = true
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
predicate = nil
lock.unlock()
subscription.cancel()
}
var description: String { return "DropWhile" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryDropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
: Subscriber,
Subscription,
CustomStringConvertible,
Subscriber
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
var description: String { return "TryDropWhile" }
// NOTE: This class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private var status = SubscriptionStatus.awaitingSubscription
private let downstream: Downstream
private var predicate: ((Input) throws -> Bool)?
private var dropping = true
private var finished = false
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream,
predicate: @escaping (Input) throws -> Bool) {
self.downstream = downstream
self.predicate = predicate
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(subscription) = status,
let shouldDrop = predicate else {
lock.unlock()
return .none
}
let dropping = self.dropping
lock.unlock()
if dropping {
do {
if try shouldDrop(input) {
return .max(1)
} else {
lock.lock()
self.dropping = false
lock.unlock()
}
} catch {
lock.lock()
status = .terminal
predicate = nil
finished = true
lock.unlock()
subscription.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
downstream.receive(completion: completion.eraseError())
isCompleted = true
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
let wasFinished = finished
finished = true
lock.unlock()
if !wasFinished {
downstream.receive(completion: completion.eraseError())
}
}
}
}
extension Publisher {
/// Omits elements from the upstream publisher until a given closure returns false,
/// before republishing all remaining elements.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the publishers
/// output.
/// - Returns: A publisher that skips over elements until the provided closure returns
/// `false`.
public func drop(
while predicate: @escaping (Output) -> Bool
) -> Publishers.DropWhile<Self> {
return Publishers.DropWhile(upstream: self, predicate: predicate)
}
/// Omits elements from the upstream publisher until an error-throwing closure returns
/// false, before republishing all remaining elements.
///
/// If the predicate closure throws, the publisher fails with an error.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the publishers
/// output.
/// - Returns: A publisher that skips over elements until the provided closure returns
/// `false`, and then republishes all remaining elements. If the predicate closure
/// throws, the publisher fails with an error.
public func tryDrop(
while predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryDropWhile<Self> {
return Publishers.TryDropWhile(upstream: self, predicate: predicate)
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
predicate = nil
finished = true
lock.unlock()
subscription.cancel()
}
var description: String { return "TryDropWhile" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
@@ -1,109 +0,0 @@
//
// Publishers.Encode.swift
//
//
// Created by Joseph Spadafora on 6/22/19.
//
extension Publishers {
public struct Encode<Upstream, Coder>: Publisher
where Upstream: Publisher,
Coder: TopLevelEncoder,
Upstream.Output: Encodable
{
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
/// The kind of values published by this publisher.
public typealias Output = Coder.Output
public let upstream: Upstream
private let encoder: Coder
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self.encoder = encoder
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
let encodeSubscriber = _Encode<Upstream, Downstream, Coder>(
downstream: subscriber,
encoder: encoder
)
upstream.subscribe(encodeSubscriber)
}
}
}
private final class _Encode<Upstream: Publisher,
Downstream: Subscriber,
Coder: TopLevelEncoder>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Coder.Output == Downstream.Input,
Upstream.Output: Encodable,
Downstream.Failure == Error {
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
private let _encoder: Coder
var description: String { return "Encode" }
init(downstream: Downstream, encoder: Coder) {
self._encoder = encoder
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
let value = try _encoder.encode(input)
return downstream.receive(value)
} catch {
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
}
extension Publisher {
public func encode<Coder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder>
where Coder: TopLevelEncoder
{
return Publishers.Encode(upstream: self, encoder: encoder)
}
}
@@ -0,0 +1,278 @@
${template_header}
//
// Publishers.Encode.swift.gyb
//
//
// Created by Joseph Spadafora on 6/22/19.
//
%{
instantiations = ['Encode', 'Decode']
}%
extension Publisher {
/// Encodes the output from upstream using a specified encoder.
///
/// Use `encode(encoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
/// property lists) to encode an `Encodable` struct into `Data` that could be used to
/// make a JSON string (or written to disk as a binary plist in the case of property
/// lists).
///
/// In this example, a `PassthroughSubject` publishes an `Article`.
/// The `encode(encoder:)` operator encodes the properties of the `Article` struct
/// into a new JSON string according to the `Codable` protocol adopted by `Article`.
/// The operator publishes the resulting JSON string to the downstream subscriber.
/// If the encoding operation fails, which can happen in the case of complex
/// properties that cant be directly transformed into JSON, the stream terminates
/// and the error is passed to the downstream subscriber.
///
/// struct Article: Codable {
/// let title: String
/// let author: String
/// let pubDate: Date
/// }
///
/// let dataProvider = PassthroughSubject<Article, Never>()
/// let cancellable = dataProvider
/// .encode(encoder: JSONEncoder())
/// .sink(receiveCompletion: { print ("Completion: \($0)") },
/// receiveValue: { data in
/// guard let stringRepresentation =
/// String(data: data, encoding: .utf8) else { return }
/// print("""
/// Data received \(data) string representation: \
/// \(stringRepresentation)
/// """)
/// })
///
/// dataProvider.send(Article(title: "My First Article",
/// author: "Gita Kumar",
/// pubDate: Date()))
///
/// // Prints: "Data received 86 bytes string representation:
/// // {"title":"My First Article","author":"Gita Kumar"
/// // "pubDate":606211803.279603}"
///
/// - Parameter encoder: An encoder that implements the `TopLevelEncoder` protocol.
/// - Returns: A publisher that encodes received elements using a specified encoder,
/// and publishes the resulting data.
public func encode<Coder: TopLevelEncoder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder> {
return .init(upstream: self, encoder: encoder)
}
/// Decodes the output from the upstream using a specified decoder.
///
/// Use `decode(type:decoder:)` with a `JSONDecoder` (or a `PropertyListDecoder` for
/// property lists) to decode data received from a `URLSession.DataTaskPublisher` or
/// other data source using the `Decodable` protocol.
///
/// In this example, a `PassthroughSubject` publishes a JSON string. The JSON decoder
/// parses the string, converting its fields according to the `Decodable` protocol
/// implemented by `Article`, and successfully populating a new `Article`.
/// The `Publishers.Decode` publisher then publishes the `Article` to the downstream.
/// If a decoding operation fails, which happens in the case of missing or malformed
/// data in the source JSON string, the stream terminates and passes the error to
/// the downstream subscriber.
///
/// struct Article: Codable {
/// let title: String
/// let author: String
/// let pubDate: Date
/// }
///
/// let dataProvider = PassthroughSubject<Data, Never>()
/// cancellable = dataProvider
/// .decode(type: Article.self, decoder: JSONDecoder())
/// .sink(receiveCompletion: { print ("Completion: \($0)")},
/// receiveValue: { print ("value: \($0)") })
///
/// dataProvider.send(Data("""
/// {\"pubDate\":1574273638.575666, \
/// \"title\" : \"My First Article\", \
/// \"author\" : \"Gita Kumar\" }
/// """.utf8))
///
/// // Prints:
/// // ".sink() data received Article(title: "My First Article",
/// // author: "Gita Kumar",
/// // pubDate: 2050-11-20 18:13:58 +0000)"
///
/// - Parameters:
/// - type: The encoded data to decode into a struct that conforms to
/// the `Decodable` protocol.
/// - decoder: A decoder that implements the `TopLevelDecoder` protocol.
/// - Returns: A publisher that decodes a given type using a specified decoder and
/// publishes the result.
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
type: Item.Type,
decoder: Coder
) -> Publishers.Decode<Self, Item, Coder> where Output == Coder.Input {
return .init(upstream: self, decoder: decoder)
}
}
extension Publishers {
public struct Encode<Upstream: Publisher, Coder: TopLevelEncoder>: Publisher
where Upstream.Output: Encodable
{
public typealias Failure = Error
public typealias Output = Coder.Output
public let upstream: Upstream
private let _encode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self._encode = encoder.encode
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, encode: _encode))
}
}
public struct Decode<Upstream: Publisher, Output: Decodable, Coder: TopLevelDecoder>
: Publisher
where Upstream.Output == Coder.Input
{
public typealias Failure = Error
public let upstream: Upstream
private let _decode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decode = { try decoder.decode(Output.self, from: $0) }
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, decode: _decode))
}
}
}
% for instantiation in instantiations:
extension Publishers.${instantiation} {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let ${instantiation.lower()}: (Upstream.Output) throws -> Output
private let lock = UnfairLock.allocate()
private var finished = false
private var subscription: Subscription?
fileprivate init(
downstream: Downstream,
${instantiation.lower()}: @escaping (Upstream.Output) throws -> Output
) {
self.downstream = downstream
self.${instantiation.lower()} = ${instantiation.lower()}
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
if finished || self.subscription != nil {
lock.unlock()
subscription.cancel()
return
}
self.subscription = subscription
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
if finished {
lock.unlock()
return .none
}
lock.unlock()
do {
return try downstream.receive(${instantiation.lower()}(input))
} catch {
lock.lock()
finished = true
let subscription = self.subscription
self.subscription = nil
lock.unlock()
subscription?.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
if finished {
lock.unlock()
return
}
finished = true
subscription = nil
lock.unlock()
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
let subscription = self.subscription
lock.unlock()
subscription?.request(demand)
}
func cancel() {
lock.lock()
guard !finished, let subscription = self.subscription else {
lock.unlock()
return
}
self.subscription = nil
finished = true
lock.unlock()
subscription.cancel()
}
var description: String { return "${instantiation}" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("finished", finished),
("upstreamSubscription", subscription as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
% end
@@ -9,6 +9,20 @@ extension Publisher {
/// Republishes all elements that match a provided closure.
///
/// OpenCombines `filter(_:)` operator performs an operation similar to that of
/// `filter(_:)` in the Swift Standard Library: it uses a closure to test each element
/// to determine whether to republish the element to the downstream subscriber.
///
/// The following example, uses a filter operation that receives an `Int` and only
/// republishes a value if its even.
///
/// let numbers: [Int] = [1, 2, 3, 4, 5]
/// cancellable = numbers.publisher
/// .filter { $0 % 2 == 0 }
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "2 4"
///
/// - Parameter isIncluded: A closure that takes one element and returns
/// a Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
@@ -20,11 +34,34 @@ extension Publisher {
/// Republishes all elements that match a provided error-throwing closure.
///
/// Use `tryFilter(_:)` to filter elements evaluated in an error-throwing closure.
/// If the `isIncluded` closure throws an error, the publisher fails with that error.
///
/// - Parameter isIncluded: A closure that takes one element and returns a
/// Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
/// In the example below, `tryFilter(_:)` checks to see if the divisor provided by
/// the publisher is zero, and throws a `DivisionByZeroError` and then terminates
/// the publisher with the thrown error:
///
/// struct DivisionByZeroError: Error {}
///
/// let numbers: [Int] = [1, 2, 3, 4, 0, 5]
/// cancellable = numbers.publisher
/// .tryFilter {
/// if $0 == 0 {
/// throw DivisionByZeroError()
/// } else {
/// return $0 % 2 == 0
/// }
/// }
/// .sink(
/// receiveCompletion: { print ("\($0)") },
/// receiveValue: { print ("\($0)", terminator: " ") }
/// )
///
/// // Prints: "2 4 failure(DivisionByZeroError())".
///
/// - Parameter isIncluded: A closure that takes one element and returns a Boolean
/// value that indicated whether to republish the element or throws an error.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Self> {
@@ -97,8 +134,7 @@ extension Publishers {
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let filter = Inner(downstream: subscriber, isIncluded: catching(isIncluded))
upstream.receive(subscriber: filter)
upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded))
}
}
@@ -137,92 +173,76 @@ extension Publishers {
where Upstream.Output == Downstream.Input,
Downstream.Failure == Failure
{
let filter = Inner(downstream: subscriber, isIncluded: catching(isIncluded))
upstream.receive(subscriber: filter)
upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded))
}
}
}
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 struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private final class Inner<Downstream: Subscriber>
: _Filter<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure {
private let downstream: Downstream
private let filter: (Input) -> Bool
let combineIdentifier = CombineIdentifier()
init(downstream: Downstream, filter: @escaping (Input) -> Bool) {
self.downstream = downstream
self.filter = filter
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
if filter(input) {
return downstream.receive(input)
}
return .max(1)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "Filter" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isFinished else { return }
downstream.receive(completion: completion)
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
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())
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
{
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Upstream.Output?, Error> {
do {
return try filter(newValue) ? .continue(newValue) : .continue(nil)
} catch {
return .failure(error)
}
}
override var description: String { return "TryFilter" }
}
}
@@ -9,38 +9,89 @@ extension Publisher {
/// Publishes the first element of a stream, then finishes.
///
/// If this publisher doesnt receive any elements, it finishes without publishing.
/// Use `first()` to publish just the first element from an upstream publisher, then
/// finish normally. The `first()` operator requests `Subscribers.Demand.unlimited`
/// from its upstream as soon as downstream requests at least one element.
/// If the upstream completes before `first()` receives any elements, it completes
/// without emitting any values.
///
/// In this example, the `first()` publisher republishes the first element received
/// from the sequence publisher, `-10`, then finishes normally.
///
/// let numbers = (-10...10)
/// cancellable = numbers.publisher
/// .first()
/// .sink { print("\($0)") }
///
/// // Print: "-10"
///
/// - Returns: A publisher that only publishes the first element of a stream.
public func first() -> Publishers.First<Self> {
return .init(upstream: self)
}
/// Publishes the first element of a stream to
/// satisfy a predicate closure, then finishes.
/// Publishes the first element of a stream to satisfy a predicate closure, then
/// finishes normally.
///
/// The publisher ignores all elements after the first.
/// If this publisher doesnt 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.
/// Use `first(where:)` to republish only the first element of a stream that satisfies
/// a closure you specify. The publisher ignores all elements after the first element
/// that satisfies the closure and finishes normally.
/// If this publisher doesnt receive any elements, it finishes without publishing.
///
/// In the example below, the provided closure causes the `Publishers.FirstWhere`
/// publisher to republish the first received element thats greater than `0`,
/// then finishes normally.
///
/// let numbers = (-10...10)
/// cancellable = numbers.publisher
/// .first { $0 > 0 }
/// .sink { print("\($0)") }
///
/// // Prints: "1"
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the first element of a stream that
/// satisfies the predicate.
public func first(
where predicate: @escaping (Output) -> Bool
) -> Publishers.FirstWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes the first element of a stream to satisfy a
/// throwing predicate closure, then finishes.
/// Publishes the first element of a stream to satisfy a throwing predicate closure,
/// then finishes normally.
///
/// The publisher ignores all elements after the first. If this publisher
/// doesnt 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.
/// Use `tryFirst(where:)` when you need to republish only the first element of
/// a stream that satisfies an error-throwing closure you specify.
/// The publisher ignores all elements after the first. If this publisher doesnt
/// receive any elements, it finishes without publishing. If the predicate closure
/// throws an error, the publisher fails.
///
/// In the example below, a range publisher emits the first element in the range then
/// finishes normally:
///
/// let numberRange: ClosedRange<Int> = (-1...50)
/// numberRange.publisher
/// .tryFirst {
/// guard $0 < 99 else {throw RangeError()}
/// return true
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)", terminator: " ") },
/// receiveValue: { print ("\($0)", terminator: " ") }
/// )
///
/// // Prints: "-1 completion: finished"
/// // If instead the number range were ClosedRange<Int> = (100...200),
/// // the tryFirst operator would terminate publishing with a RangeError.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the first element of a stream that
/// satisfies the predicate.
public func tryFirst(
where predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryFirstWhere<Self> {
@@ -4,20 +4,58 @@
// 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.
/// Transforms all elements from an upstream publisher into a new publisher up to
/// a maximum number of publishers you specify.
///
/// `flatMap` merges the output from all returned publishers into a single stream of
/// output.
/// OpenCombines `flatMap(maxPublishers:_:)` operator performs a similar function
/// to the `flatMap(_:)` operator in the Swift standard library, but turns
/// the elements from one kind of publisher into a new publisher that is sent
/// to subscribers. Use `flatMap(maxPublishers:_:)` when you want to create a new
/// series of events for downstream subscribers based on the received value.
/// The closure creates the new `Publishe`` based on the received value.
/// The new `Publisher` can emit more than one event, and successful completion of
/// the new `Publisher` does not complete the overall stream.
/// Failure of the new `Publisher` will fail the overall stream.
///
/// In the example below, a `PassthroughSubject` publishes `WeatherStation` elements.
/// The `flatMap(maxPublishers:_:)` receives each element, creates a `URL` from it,
/// and produces a new `URLSession.DataTaskPublisher`, which will publish the data
/// loaded from that `URL`.
///
/// public struct WeatherStation {
/// public let stationID: String
/// }
///
/// var weatherPublisher = PassthroughSubject<WeatherStation, URLError>()
///
/// cancellable = weatherPublisher
/// .flatMap { station -> URLSession.DataTaskPublisher in
/// let url = URL(string: """
/// https://weatherapi.example.com/stations/\(station.stationID)\
/// /observations/latest
/// """)!
/// return URLSession.shared.dataTaskPublisher(for: url)
/// }
/// .sink(
/// receiveCompletion: { completion in
/// // Handle publisher completion (normal or error).
/// },
/// receiveValue: {
/// // Process the received data.
/// }
/// )
///
/// weatherPublisher.send(WeatherStation(stationID: "KSFO")) // San Francisco, CA
/// weatherPublisher.send(WeatherStation(stationID: "EGLC")) // London, UK
/// weatherPublisher.send(WeatherStation(stationID: "ZBBB")) // Beijing, CN
///
/// - Parameters:
/// - maxPublishers: The maximum number of publishers produced by this method.
/// - transform: A closure that takes an element as a parameter and returns a
/// publisher that produces elements of that type.
/// - maxPublishers: Specifies the maximum number of concurrent publisher
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
/// - transform: A closure that takes an element as a parameter and returns
/// a publisher that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher into
/// a publisher of that elements type.
public func flatMap<Result, Child: Publisher>(
@@ -25,23 +63,83 @@ extension Publisher {
_ transform: @escaping (Output) -> Child
) -> Publishers.FlatMap<Child, Self>
where Result == Child.Output, Failure == Child.Failure {
return Publishers.FlatMap(upstream: self,
maxPublishers: maxPublishers,
transform: transform)
return .init(upstream: self,
maxPublishers: maxPublishers,
transform: transform)
}
}
extension Publisher where Failure == Never {
/// Transforms all elements from an upstream publisher into a new publisher up to
/// a maximum number of publishers you specify.
///
/// - Parameters:
/// - maxPublishers: Specifies the maximum number of concurrent publisher
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
/// - transform: A closure that takes an element as a parameter and returns
/// a publisher that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher into
/// a publisher of that elements type.
public func flatMap<Child: Publisher>(
maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Output) -> Child
) -> Publishers.FlatMap<Child, Publishers.SetFailureType<Self, Child.Failure>> {
return setFailureType(to: Child.Failure.self)
.flatMap(maxPublishers: maxPublishers, transform)
}
/// Transforms all elements from an upstream publisher into a new publisher up to
/// a maximum number of publishers you specify.
///
/// - Parameters:
/// - maxPublishers: Specifies the maximum number of concurrent publisher
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
/// - transform: A closure that takes an element as a parameter and returns
/// a publisher that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher
/// into a publisher of that elements type.
public func flatMap<Child: Publisher>(
maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Output) -> Child
) -> Publishers.FlatMap<Child, Self> where Child.Failure == Never {
return .init(upstream: self, maxPublishers: maxPublishers, transform: transform)
}
}
extension Publisher {
/// Transforms all elements from an upstream publisher into a new publisher up to
/// a maximum number of publishers you specify.
///
/// - Parameters:
/// - maxPublishers: Specifies the maximum number of concurrent publisher
/// subscriptions, or `Subscribers.Demand.unlimited` if unspecified.
/// - transform: A closure that takes an element as a parameter and returns
/// a publisher that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher into
/// a publisher of that elements type.
public func flatMap<Child: Publisher>(
maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Output) -> Child
) -> Publishers.FlatMap<Publishers.SetFailureType<Child, Failure>, Self>
where Child.Failure == Never
{
return flatMap(maxPublishers: maxPublishers) {
transform($0).setFailureType(to: Failure.self)
}
}
}
extension Publishers {
/// A publisher that transforms elements from an upstream publisher into a new
/// publisher.
public struct FlatMap<Child: Publisher, Upstream: Publisher>: Publisher
where Child.Failure == Upstream.Failure
{
/// 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
@@ -56,363 +154,384 @@ extension Publishers {
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,
let outer = Outer(downstream: subscriber,
maxPublishers: maxPublishers,
transform: transform)
upstream.subscribe(inner)
map: transform)
subscriber.receive(subscription: outer)
upstream.subscribe(outer)
}
}
}
extension Publishers.FlatMap {
fileprivate final class Inner<Downstream: Subscriber>
: CustomStringConvertible,
Cancellable
private final class Outer<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Child.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private typealias 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 typealias SubscriptionIndex = Int
/// All requests to this subscription should be made with the `outerLock`
/// acquired.
private var outerSubscription: Subscription?
/// The lock for requesting from `outerSubscription`.
private let outerLock = UnfairRecursiveLock.allocate()
/// The lock for modifying the state. All mutable state here should be
/// read and modified with this lock acquired.
/// The only exception is the `downstreamRecursive` field, which is guarded
/// by the `downstreamLock`.
private let lock = UnfairLock.allocate()
/// All the calls to the downstream subscriber should be made with this lock
/// acquired.
private let downstreamLock = UnfairRecursiveLock.allocate()
private let downstream: Downstream
private var downstreamDemand = Subscribers.Demand.none
/// This variable is set to `true` whenever we call `downstream.receive(_:)`,
/// and then set back to `false`.
private var downstreamRecursive = false
private var innerRecursive = false
private var subscriptions = [SubscriptionIndex : Subscription]()
private var nextInnerIndex: SubscriptionIndex = 0
private var pendingSubscriptions = 0
private var buffer = [(SubscriptionIndex, Child.Output)]()
private let maxPublishers: Subscribers.Demand
private let 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" }
private let map: (Input) -> Child
private var cancelledOrCompleted = false
private var outerFinished = false
init(downstream: Downstream,
maxPublishers: Subscribers.Demand,
transform: @escaping (Upstream.Output) -> Child) {
map: @escaping (Upstream.Output) -> Child) {
self.downstream = downstream
self.maxPublishers = maxPublishers
self.transform = transform
self.map = map
}
deinit {
outerLock.deallocate()
lock.deallocate()
downstreamLock.deallocate()
}
final func cancel() {
// MARK: - Subscriber
let (upstreamToCancel, childrenToCancel) = lock
.do { () -> (Subscription?, Set<ChildSubscriber>) in
let upstreamToCancel = upstreamSubscription
upstreamSubscription = nil
return (upstreamToCancel, lockedDeactivateAndReturnChildrenToCancel())
fileprivate func receive(subscription: Subscription) {
lock.lock()
guard outerSubscription == nil, !cancelledOrCompleted else {
lock.unlock()
subscription.cancel()
return
}
outerSubscription = subscription
lock.unlock()
subscription.request(maxPublishers)
}
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
let cancelledOrCompleted = self.cancelledOrCompleted
lock.unlock()
if cancelledOrCompleted {
return .none
}
let child = map(input)
lock.lock()
let innerIndex = nextInnerIndex
nextInnerIndex += 1
pendingSubscriptions += 1
lock.unlock()
child.subscribe(Side(index: innerIndex, inner: self))
return .none
}
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
outerSubscription = nil
outerFinished = true
switch completion {
case .finished:
releaseLockThenSendCompletionDownstreamIfNeeded(outerFinished: true)
return
case .failure:
let wasAlreadyCancelledOrCompleted = cancelledOrCompleted
cancelledOrCompleted = true
for (_, subscription) in subscriptions {
// Cancelling subscriptions with the lock acquired. Not good,
// but that's what Combine does. This code path is tested.
subscription.cancel()
}
upstreamToCancel?.cancel()
cancelChildren(childrenToCancel)
subscriptions = [:]
lock.unlock()
if wasAlreadyCancelledOrCompleted {
return
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
}
}
}
// Private implementation
extension Publishers.FlatMap.Inner {
// MARK: - Subscription
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
fileprivate func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
if downstreamRecursive {
// downstreamRecursive being true means that downstreamLock
// is already acquired.
downstreamDemand += demand
return
}
lock.lock()
if cancelledOrCompleted {
lock.unlock()
return
}
if demand == .unlimited {
downstreamDemand = .unlimited
let buffer = self.buffer
self.buffer = []
let subscriptions = self.subscriptions
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
for (_, childOutput) in buffer {
_ = downstream.receive(childOutput)
}
downstreamRecursive = false
downstreamLock.unlock()
for (_, subscription) in subscriptions {
subscription.request(.unlimited)
}
lock.lock()
} else {
downstreamDemand += demand
while !buffer.isEmpty && downstreamDemand > 0 {
// FIXME: This has quadratic complexity.
// This is what Combine does.
// Can we improve perfomance by using e. g. Deque instead of Array?
// Or array's cache locality makes this solution more efficient?
// Must benchmark before optimizing!
//
// https://www.cocoawithlove.com/blog/2016/09/22/deque.html
let (index, value) = buffer.removeFirst()
downstreamDemand -= 1
let subscription = subscriptions[index]
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
let additionalDemand = downstream.receive(value)
downstreamRecursive = false
downstreamLock.unlock()
if additionalDemand != .none {
lock.lock()
downstreamDemand += additionalDemand
lock.unlock()
}
if let subscription = subscription {
innerRecursive = true
subscription.request(.max(1))
innerRecursive = false
}
lock.lock()
}
}
releaseLockThenSendCompletionDownstreamIfNeeded(outerFinished: outerFinished)
}
fileprivate func cancel() {
lock.lock()
if cancelledOrCompleted {
lock.unlock()
return
}
cancelledOrCompleted = true
let subscriptions = self.subscriptions
self.subscriptions = [:]
let outerSubscription = self.outerSubscription
self.outerSubscription = nil
lock.unlock()
for (_, subscription) in subscriptions {
subscription.cancel()
}
// Combine doesn't acquire outerLock here. Weird.
outerSubscription?.cancel()
}
// MARK: - Reflection
fileprivate var description: String { return "FlatMap" }
fileprivate var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
fileprivate var playgroundDescription: Any { return description }
// MARK: - Private
private func receiveInner(subscription: Subscription,
_ index: SubscriptionIndex) {
lock.lock()
pendingSubscriptions -= 1
subscriptions[index] = subscription
let demand = downstreamDemand == .unlimited
? Subscribers.Demand.unlimited
: .max(1)
lock.unlock()
subscription.request(demand)
}
private func receiveInner(_ input: Child.Output,
_ index: SubscriptionIndex) -> Subscribers.Demand {
lock.lock()
if downstreamDemand == .unlimited {
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
_ = downstream.receive(input)
downstreamRecursive = false
downstreamLock.unlock()
return .none
}
if downstreamDemand == .none || innerRecursive {
buffer.append((index, input))
lock.unlock()
return .none
}
downstreamDemand -= 1
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
let newDemand = downstream.receive(input)
downstreamRecursive = false
downstreamLock.unlock()
if newDemand > 0 {
lock.lock()
downstreamDemand += newDemand
lock.unlock()
}
return .max(1)
}
private func receiveInner(completion: Subscribers.Completion<Child.Failure>,
_ index: SubscriptionIndex) {
switch completion {
case .finished:
lock.lock()
subscriptions.removeValue(forKey: index)
let downstreamCompleted = releaseLockThenSendCompletionDownstreamIfNeeded(
outerFinished: outerFinished
)
if !downstreamCompleted {
requestOneMorePublisher()
}
case .failure:
lock.lock()
if cancelledOrCompleted {
lock.unlock()
return
}
cancelledOrCompleted = true
let subscriptions = self.subscriptions
self.subscriptions = [:]
lock.unlock()
for (i, subscription) in subscriptions where i != index {
subscription.cancel()
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
}
private func requestOneMorePublisher() {
if maxPublishers != .unlimited {
outerLock.lock()
outerSubscription?.request(.max(1))
outerLock.unlock()
}
}
/// - Precondition: `lock` is acquired
/// - Postcondition: `lock` is released
///
/// - Returns: `true` if a completion was sent downstream
@discardableResult
private func releaseLockThenSendCompletionDownstreamIfNeeded(
outerFinished: Bool
) -> Bool {
#if DEBUG
lock.assertOwner() // Sanity check
#endif
if !cancelledOrCompleted && outerFinished && buffer.isEmpty &&
subscriptions.count + pendingSubscriptions == 0 {
cancelledOrCompleted = true
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: .finished)
downstreamLock.unlock()
return true
}
lock.unlock()
return false
}
if shouldProcessQueue {
processQueue()
}
}
// MARK: - Side
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 struct Side: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible {
private let index: SubscriptionIndex
private let inner: Outer
fileprivate let combineIdentifier = CombineIdentifier()
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)
fileprivate init(index: SubscriptionIndex, inner: Outer) {
self.index = index
self.inner = inner
}
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 }
}
fileprivate func receive(subscription: Subscription) {
inner.receiveInner(subscription: subscription, index)
}
fileprivate func receive(_ input: Child.Output) -> Subscribers.Demand {
return inner.receiveInner(input, index)
}
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
inner.receiveInner(completion: completion, index)
}
fileprivate var description: String { return "FlatMap" }
fileprivate var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(
("parentSubscription", inner.combineIdentifier)
)
return Mirror(self, children: children)
}
fileprivate var playgroundDescription: Any { return description }
}
}
}
// 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,260 @@
//
// Publishers.HandleEvents.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
extension Publisher {
/// Performs the specified closures when publisher events occur.
///
/// Use `handleEvents` when you want to examine elements as they progress through
/// the stages of the publishers lifecycle.
///
/// In the example below, a publisher of integers shows the effect of printing
/// debugging information at each stage of the element-processing lifecycle:
///
/// let integers = (0...2)
/// cancellable = integers.publisher
/// .handleEvents(receiveSubscription: { subs in
/// print("Subscription: \(subs.combineIdentifier)")
/// }, receiveOutput: { anInt in
/// print("in output handler, received \(anInt)")
/// }, receiveCompletion: { _ in
/// print("in completion handler")
/// }, receiveCancel: {
/// print("received cancel")
/// }, receiveRequest: { (demand) in
/// print("received demand: \(demand.description)")
/// })
/// .sink { _ in return }
///
/// // Prints:
/// // received demand: unlimited
/// // Subscription: 0x7f81284734c0
/// // in output handler, received 0
/// // in output handler, received 1
/// // in output handler, received 2
/// // in completion handler
///
/// - Parameters:
/// - receiveSubscription: A closure that executes when the publisher receives
/// the subscription from the upstream publisher. Defaults to `nil`.
/// - receiveOutput: A closure that executes when the publisher receives a value
/// from the upstream publisher. Defaults to `nil`.
/// - receiveCompletion: A closure that executes when the publisher receives
/// the completion from the upstream publisher. Defaults to `nil`.
/// - receiveCancel: A closure that executes when the downstream receiver cancels
/// publishing. Defaults to `nil`.
/// - receiveRequest: A closure that executes when the publisher receives a request
/// for more elements. Defaults to `nil`.
/// - Returns: A publisher that performs the specified closures when publisher events
/// occur.
public func handleEvents(
receiveSubscription: ((Subscription) -> Void)? = nil,
receiveOutput: ((Output) -> Void)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
receiveCancel: (() -> Void)? = nil,
receiveRequest: ((Subscribers.Demand) -> Void)? = nil
) -> Publishers.HandleEvents<Self> {
return .init(upstream: self,
receiveSubscription: receiveSubscription,
receiveOutput: receiveOutput,
receiveCompletion: receiveCompletion,
receiveCancel: receiveCancel,
receiveRequest: receiveRequest)
}
}
extension Publishers {
/// A publisher that performs the specified closures when publisher events occur.
public struct HandleEvents<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that executes when the publisher receives the subscription from
/// the upstream publisher.
public var receiveSubscription: ((Subscription) -> Void)?
/// A closure that executes when the publisher receives a value from the upstream
/// publisher.
public var receiveOutput: ((Upstream.Output) -> Void)?
/// A closure that executes when the publisher receives the completion from
/// the upstream publisher.
public var receiveCompletion:
((Subscribers.Completion<Upstream.Failure>) -> Void)?
/// A closure that executes when the downstream receiver cancels publishing.
public var receiveCancel: (() -> Void)?
/// A closure that executes when the publisher receives a request for more
/// elements.
public var receiveRequest: ((Subscribers.Demand) -> Void)?
public init(
upstream: Upstream,
receiveSubscription: ((Subscription) -> Void)? = nil,
receiveOutput: ((Output) -> Void)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
receiveCancel: (() -> Void)? = nil,
receiveRequest: ((Subscribers.Demand) -> Void)?
) {
self.upstream = upstream
self.receiveSubscription = receiveSubscription
self.receiveOutput = receiveOutput
self.receiveCompletion = receiveCompletion
self.receiveCancel = receiveCancel
self.receiveRequest = receiveRequest
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(self, downstream: subscriber)
upstream.subscribe(inner)
}
}
}
extension Publishers.HandleEvents {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
public var receiveSubscription: ((Subscription) -> Void)?
public var receiveOutput: ((Upstream.Output) -> Void)?
public var receiveCompletion:
((Subscribers.Completion<Upstream.Failure>) -> Void)?
public var receiveCancel: (() -> Void)?
public var receiveRequest: ((Subscribers.Demand) -> Void)?
private let downstream: Downstream
init(_ events: Publishers.HandleEvents<Upstream>, downstream: Downstream) {
self.receiveSubscription = events.receiveSubscription
self.receiveOutput = events.receiveOutput
self.receiveCompletion = events.receiveCompletion
self.receiveCancel = events.receiveCancel
self.receiveRequest = events.receiveRequest
self.downstream = downstream
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
if let receiveSubscription = self.receiveSubscription {
lock.unlock()
receiveSubscription(subscription)
lock.lock()
}
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
if let receiveOutput = self.receiveOutput {
lock.unlock()
receiveOutput(input)
} else {
lock.unlock()
}
let newDemand = downstream.receive(input)
if newDemand == .none {
return newDemand
}
lock.lock()
if let receiveRequest = self.receiveRequest {
lock.unlock()
receiveRequest(newDemand)
} else {
lock.unlock()
}
return newDemand
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
if let receiveCompletion = self.receiveCompletion {
lock.unlock()
receiveCompletion(completion)
lock.lock()
}
lockedTerminate()
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
if let receiveRequest = self.receiveRequest {
lock.unlock()
receiveRequest(demand)
lock.lock()
}
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
if let receiveCancel = self.receiveCancel {
lock.unlock()
receiveCancel()
lock.lock()
}
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lockedTerminate()
lock.unlock()
subscription.cancel()
}
var description: String { return "HandleEvents" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
private func lockedTerminate() {
receiveSubscription = nil
receiveOutput = nil
receiveCompletion = nil
receiveCancel = nil
receiveRequest = nil
status = .terminal
}
}
}
@@ -6,13 +6,36 @@
extension Publisher {
/// Ingores all upstream elements, but passes along a completion
/// state (finished or failed).
/// Ingores all upstream elements, but passes along a completion state (finished or
/// failed).
///
/// Use the `ignoreOutput(`` operator to determine if a publisher is able to complete
/// successfully or would fail.
///
/// In the example below, the array publisher (`numbers`) delivers the first five of
/// its elements successfully, as indicated by the `ignoreOutput()` operator.
/// The operator consumes, but doesnt republish the elements downstream. However,
/// the sixth element, `0`, causes the error throwing closure to catch
/// a `NoZeroValuesAllowedError` that terminates the stream.
///
/// struct NoZeroValuesAllowedError: Error {}
/// let numbers = [1, 2, 3, 4, 5, 0, 6, 7, 8, 9]
/// cancellable = numbers.publisher
/// .tryFilter({ anInt in
/// guard anInt != 0 else { throw NoZeroValuesAllowedError() }
/// return anInt < 20
/// })
/// .ignoreOutput()
/// .sink(receiveCompletion: {print("completion: \($0)")},
/// receiveValue: {print("value \($0)")})
///
/// // Prints: "completion: failure(NoZeroValuesAllowedError())"
///
/// The output type of this publisher is `Never`.
///
/// - Returns: A publisher that ignores all upstream elements.
public func ignoreOutput() -> Publishers.IgnoreOutput<Self> {
return Publishers.IgnoreOutput(upstream: self)
return .init(upstream: self)
}
}
@@ -21,12 +44,8 @@ extension Publishers {
/// 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.
@@ -36,40 +55,36 @@ extension Publishers {
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)
where Downstream.Failure == Upstream.Failure, Downstream.Input == Never
{
upstream.subscribe(Inner<Downstream>(downstream: subscriber))
}
}
}
extension Publishers.IgnoreOutput {
private final class Inner<Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
Subscription
where Downstream.Input == Never,
Downstream.Failure == Upstream.Failure
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Never, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Output = Never
typealias Failure = Upstream.Failure
var description: String { return "IgnoreOutput" }
private let downstream: Downstream
let combineIdentifier = CombineIdentifier()
fileprivate init(downstream: Downstream) {
self.downstream = downstream
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
downstream.receive(subscription: subscription)
subscription.request(.unlimited)
}
@@ -81,10 +96,13 @@ extension Publishers.IgnoreOutput {
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
// ignore and requests from downstream since we'll never send
// any values
var description: String { return "IgnoreOutput" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -7,33 +7,82 @@
extension Publisher {
/// Only publishes the last element of a stream, after the stream finishes.
/// Publishes the last element of a stream, after the stream finishes.
///
/// Use `last()` when you need to emit only the last element from an upstream
/// publisher.
///
/// In the example below, the range publisher only emits the last element from
/// the sequence publisher, `10`, then finishes normally.
///
/// let numbers = (-10...10)
/// cancellable = numbers.publisher
/// .last()
/// .sink { print("\($0)") }
///
/// // Prints: "10"
///
/// - Returns: A publisher that only publishes the last element of a stream.
public func last() -> Publishers.Last<Self> {
return .init(upstream: self)
}
/// Only publishes the last element of a stream that satisfies a predicate closure,
/// after the stream finishes.
/// Publishes the last element of a stream that satisfies a predicate closure,
/// after upstream finishes.
///
/// Use `last(where:)` when you need to republish only the last element of a stream
/// that satisfies a closure you specify.
///
/// In the example below, a range publisher emits the last element that satisfies
/// the closures criteria, then finishes normally:
///
/// let numbers = (-10...10)
/// cancellable = numbers.publisher
/// .last { $0 < 6 }
/// .sink { print("\($0)") }
///
/// // Prints: "5"
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether to publish the element.
/// - Returns: A publisher that only publishes the last element satisfying
/// the given predicate.
/// returns a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the last element satisfying the given
/// predicate.
public func last(
where predicate: @escaping (Output) -> Bool
) -> Publishers.LastWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Only publishes the last element of a stream that satisfies an error-throwing
/// predicate closure, after the stream finishes.
/// Publishes the last element of a stream that satisfies an error-throwing predicate
/// closure, after the stream finishes.
///
/// Use `tryLast(where:)` when you need to republish the last element that satisfies
/// an error-throwing closure you specify. If the predicate closure throws an error,
/// the publisher fails.
///
/// In the example below, a publisher emits the last element that satisfies
/// the error-throwing closure, then finishes normally:
///
/// struct RangeError: Error {}
///
/// let numbers = [-62, 1, 6, 10, 9, 22, 41, -1, 5]
/// cancellable = numbers.publisher
/// .tryLast {
/// guard 0 != 0 else {throw RangeError()}
/// return true
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)", terminator: " ") },
/// receiveValue: { print ("\($0)", terminator: " ") }
/// )
/// // Prints: "5 completion: finished"
/// // If instead the numbers array had contained a `0`, the `tryLast` operator
/// // would terminate publishing with a RangeError."
///
/// If the predicate closure throws, the publisher fails with the thrown error.
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether to publish the element.
/// - Returns: A publisher that only publishes the last element satisfying
/// the given predicate.
/// returns a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the last element satisfying the given
/// predicate.
public func tryLast(
where predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryLastWhere<Self> {
@@ -9,6 +9,34 @@ extension Publisher where Failure == Never {
/// Creates a connectable wrapper around the publisher.
///
/// In the following example, `makeConnectable()` wraps its upstream publisher
/// (an instance of `Publishers.Share`) with a `ConnectablePublisher`. Without this,
/// the first sink subscriber would receive all the elements from the sequence
/// publisher and cause it to complete before the second subscriber attaches.
/// By making the publisher connectable, the publisher doesnt produce any elements
/// until after the `connect()` call.
///
/// let subject = Just<String>("Sent")
/// let pub = subject
/// .share()
/// .makeConnectable()
/// cancellable1 = pub.sink { print ("Stream 1 received: \($0)") }
///
/// // For example purposes, use DispatchQueue to add a second subscriber
/// // a second later, and then connect to the publisher a second after that.
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
/// self.cancellable2 = pub.sink { print ("Stream 2 received: \($0)") }
/// }
/// DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
/// self.connectable = pub.connect()
/// }
/// // Prints:
/// // Stream 2 received: Sent
/// // Stream 1 received: Sent
///
/// > Note: The `connect()` operator returns a `Cancellable` instance that you must
/// retain. You can also use this instance to cancel publishing.
///
/// - Returns: A `ConnectablePublisher` wrapping this publisher.
public func makeConnectable() -> Publishers.MakeConnectable<Self> {
return .init(upstream: self)
@@ -17,6 +45,15 @@ extension Publisher where Failure == Never {
extension Publishers {
/// A publisher that provides explicit connectability to another publisher.
///
/// `Publishers.MakeConnectable` is a `ConnectablePublisher`, which allows you to
/// perform configuration before publishing any elements. Call `connect()` on this
/// publisher when you want to attach to its upstream publisher and start producing
/// elements.
///
/// Use the `makeConnectable()` operator to wrap an upstream publisher with
/// an instance of this publisher.
public struct MakeConnectable<Upstream: Publisher>: ConnectablePublisher {
public typealias Output = Upstream.Output
@@ -25,6 +62,9 @@ extension Publishers {
private let inner: Multicast<Upstream, PassthroughSubject<Output, Failure>>
/// Creates a connectable publisher, attached to the provide upstream publisher.
///
/// - Parameter upstream: The publisher from which to receive elements.
public init(upstream: Upstream) {
inner = upstream.multicast(subject: .init())
}
@@ -5,12 +5,34 @@
// Created by Anton Nazarov on 25.06.2019.
//
import COpenCombineHelpers
extension Publisher {
/// Transforms all elements from the upstream publisher with a provided closure.
///
/// OpenCombines `map(_:)` operator performs a function similar to that of `map(_:)`
/// in the Swift standard library: it uses a closure to transform each element it
/// receives from the upstream publisher. You use `map(_:)` to transform from one kind
/// of element to another.
///
/// The following example uses an array of numbers as the source for a collection
/// based publisher. A `map(_:)` operator consumes each integer from the publisher and
/// uses a dictionary to transform it from its Arabic numeral to a Roman equivalent,
/// as a `String`.
/// If the `map(_:)`s closure fails to look up a Roman numeral, it returns the string
/// `(unknown)`.
///
/// let numbers = [5, 4, 3, 2, 1, 0]
/// let romanNumeralDict: [Int : String] =
/// [1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"]
/// cancellable = numbers.publisher
/// .map { romanNumeralDict[$0] ?? "(unknown)" }
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "V IV III II I (unknown)"
///
/// If your closure can throw an error, use OpenCombines `tryMap(_:)` operator
/// instead.
///
/// - Parameter transform: A closure that takes one element as its parameter and
/// returns a new element.
/// - Returns: A publisher that uses the provided closure to map elements from
@@ -21,21 +43,78 @@ extension Publisher {
return Publishers.Map(upstream: self, transform: transform)
}
/// Transforms all elements from the upstream publisher with a provided
/// error-throwing closure.
/// Transforms all elements from the upstream publisher with a provided error-throwing
/// closure.
///
/// If the `transform` closure throws an error, the publisher fails with the thrown
/// error.
/// OpenCombines `tryMap(_:)` operator performs a function similar to that of
/// `map(_:)` in the Swift standard library: it uses a closure to transform each
/// element it receives from the upstream publisher. You use `tryMap(_:)` to transform
/// from one kind of element to another, and to terminate publishing when the maps
/// closure throws an error.
///
/// The following example uses an array of numbers as the source for a collection
/// based publisher. A `tryMap(_:)` operator consumes each integer from the publisher
/// and uses a dictionary to transform it from its Arabic numeral to a Roman
/// equivalent, as a `String`.
/// If the `tryMap(_:)`s closure fails to look up a Roman numeral, it throws
/// an error. The `tryMap(_:)` operator catches this error and terminates publishing,
/// sending a `Subscribers.Completion.failure(_:)` that wraps the error.
///
/// struct ParseError: Error {}
/// func romanNumeral(from:Int) throws -> String {
/// let romanNumeralDict: [Int : String] =
/// [1:"I", 2:"II", 3:"III", 4:"IV", 5:"V"]
/// guard let numeral = romanNumeralDict[from] else {
/// throw ParseError()
/// }
/// return numeral
/// }
/// let numbers = [5, 4, 3, 2, 1, 0]
/// cancellable = numbers.publisher
/// .tryMap { try romanNumeral(from: $0) }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") },
/// receiveValue: { print ("\($0)", terminator: " ") }
/// )
///
/// // Prints: "V IV III II I completion: failure(ParseError())"
///
/// If your closure doesnt throw, use `map(_:)` instead.
///
/// - Parameter transform: A closure that takes one element as its parameter and
/// returns a new element.
/// returns a new element. If the closure throws an error, the publisher fails with
/// the thrown error.
/// - Returns: A publisher that uses the provided closure to map elements from
/// the upstream publisher to new elements that it then publishes.
public func tryMap<Result>(
_ transform: @escaping (Self.Output) throws -> Result
_ transform: @escaping (Output) throws -> Result
) -> Publishers.TryMap<Self, Result> {
return Publishers.TryMap(upstream: self, transform: transform)
}
/// Replaces `nil` elements in the stream with the provided element.
///
/// The `replaceNil(with:)` operator enables replacement of `nil` values in a stream
/// with a substitute value. In the example below, a collection publisher contains
/// a `nil` value. The `replaceNil(with:)` operator replaces this with `0.0`.
///
/// let numbers: [Double?] = [1.0, 2.0, nil, 3.0]
/// numbers.publisher
/// .replaceNil(with: 0.0)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "Optional(1.0) Optional(2.0) Optional(0.0) Optional(3.0)"
///
/// - Parameter output: The element to use when replacing `nil`.
/// - Returns: A publisher that replaces `nil` elements from the upstream publisher
/// with the provided element.
public func replaceNil<ElementOfResult>(
with output: ElementOfResult
) -> Publishers.Map<Self, ElementOfResult>
where Output == ElementOfResult?
{
return Publishers.Map(upstream: self) { $0 ?? output }
}
}
extension Publishers {
@@ -220,13 +299,7 @@ extension Publishers.TryMap {
return try downstream.receive(map(input))
} catch {
lock.lock()
let subscription: Subscription?
switch status {
case let .subscribed(upstreamSubscription):
subscription = upstreamSubscription
case .awaitingSubscription, .terminal:
subscription = nil
}
let subscription = status.subscription
status = .terminal
lock.unlock()
subscription?.cancel()
@@ -45,15 +45,46 @@ extension Publisher {
/// Converts any failure from the upstream publisher into a new error.
///
/// Until the upstream publisher finishes normally or fails with an error,
/// the returned publisher republishes all the elements it receives.
/// Use the `mapError(_:)` operator when you need to replace one error type with
/// another, or where a downstream operator needs the error types of its inputs to
/// match.
///
/// - Parameter transform: A closure that takes the upstream failure as a
/// parameter and returns a new error for the publisher to terminate with.
/// - Returns: A publisher that replaces any upstream failure with a
/// new error produced by the `transform` closure.
/// The following example uses a `tryMap(_:)` operator to divide `1` by each element
/// produced by a sequence publisher. When the publisher produces a `0`,
/// the `tryMap(_:)` fails with a `DivisionByZeroError`. The `mapError(_:)` operator
/// converts this into a `MyGenericError`.
///
/// struct DivisionByZeroError: Error {}
/// struct MyGenericError: Error { var wrappedError: Error }
///
/// func myDivide(_ dividend: Double, _ divisor: Double) throws -> Double {
/// guard divisor != 0 else { throw DivisionByZeroError() }
/// return dividend / divisor
/// }
///
/// let divisors: [Double] = [5, 4, 3, 2, 1, 0]
/// divisors.publisher
/// .tryMap { try myDivide(1, $0) }
/// .mapError { MyGenericError(wrappedError: $0) }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)") ,
/// receiveValue: { print ("value: \($0)") }
/// )
///
/// // Prints:
/// // value: 0.2
/// // value: 0.25
/// // value: 0.3333333333333333
/// // value: 0.5
/// // value: 1.0
/// // completion: failure(MyGenericError(wrappedError: DivisionByZeroError()))"
///
/// - Parameter transform: A closure that takes the upstream failure as a parameter
/// and returns a new error for the publisher to terminate with.
/// - Returns: A publisher that replaces any upstream failure with a new error
/// produced by the `transform` closure.
public func mapError<NewFailure: Error>(
_ transform: @escaping (Self.Failure) -> NewFailure
_ transform: @escaping (Failure) -> NewFailure
) -> Publishers.MapError<Self, NewFailure>
{
return Publishers.MapError(upstream: self, transform)
@@ -12,6 +12,10 @@ from gyb_opencombine_support import (
list_with_suffix_variadic
)
import random
RNG = random.Random(0)
instantiations = [(1, '', ''),
(2, 'two', 'second '),
(3, 'three', 'third ')]
@@ -41,18 +45,66 @@ extension Publisher {
%
% publisher_name = make_publisher_name(arity)
%
% doc_cardinal = 'a keyt path' if arity == 1 else cardinal + ' key paths'
/// Returns a publisher that publishes the values of ${doc_cardinal} as a tuple.
% doc_cardinal = 'a key path' if arity == 1 else cardinal + ' key paths'
%
% doc_comment_suffix = 'value of the key path' \
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
%
% doc_output_values = 'the value of this `Int`' \
% if arity == 1 else 'these {} values (as an `({})` tuple)' \
% .format(cardinal, ', '.join(['Int'] * arity))
%
% example_dice_roll_properties = ['die'] \
% if arity == 1 else ['die{}'.format(i + 1) for i in range(arity)]
% enumerated_example_dice_roll_properties = '`die` member' \
% if arity == 1 else ('`die1` and `die2`' if arity == 2 else \
% ''.join(['`{}`, '.format(s) for s in example_dice_roll_properties[:-1]]) + \
% 'and `{}`'.format(example_dice_roll_properties[-1])) + ' members'
%
% dice_roll_init = ',\n /// ' \
% .join([s + ': Int.random(in: 1...6)' for s in example_dice_roll_properties])
/// Publishes the ${doc_comment_suffix}.
///
/// In the following example, the `map(${'_:' * arity})` operator uses the Swift
/// key path syntax to access the ${enumerated_example_dice_roll_properties}
/// of the `DiceRoll` structure published by the `Just` publisher.
///
/// The downstream sink subscriber receives only
/// ${doc_output_values},
/// not the entire `DiceRoll`.
///
/// struct DiceRoll {
% for prop in example_dice_roll_properties:
/// let ${prop}: Int
% end
/// }
///
/// cancellable = Just(DiceRoll(${dice_roll_init}))
/// .map(${', '.join(['\.' + s for s in example_dice_roll_properties])})
% if arity == 1:
/// .sink {
/// print ("Rolled: \($0)")
/// }
% else:
% closure_args = ['values.{}'.format(i) for i in range(arity)]
/// .sink { values in
/// print("""
/// Rolled: ${', '.join(['\\({})'.format(s) for s in closure_args])} \
/// (total \(${' + '.join(closure_args)}))
/// """)
/// }
% end
% random_numbers = [RNG.randint(1, 6) for _ in range(arity)]
% random_numbers_printed = ', '.join([str(i) for i in random_numbers]) + \
% (' (or some other random value)' if arity == 1 \
% else ' (total: {})" (or other random values)'.format(sum(random_numbers)))
/// // Prints "Rolled: ${random_numbers_printed}.
///
/// - Parameters:
% for i in range(arity):
% ordinal = 'another ' if i == 1 else 'a ' + instantiations[i][2]
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`
% ordinal = 'another ' if i == 1 and arity < 3 else 'a ' + instantiations[i][2]
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`.
% end
%
% doc_comment_suffix = 'value of the key path' \
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
/// - Returns: A publisher that publishes the ${doc_comment_suffix}.
public func map<${cs_result_types}>(
${method_args_joined}
@@ -0,0 +1,187 @@
//
// Publishers.MeasureInterval.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
extension Publisher {
/// Measures and emits the time interval between events received from an upstream
/// publisher.
///
/// Use `measureInterval(using:options:)` to measure the time between events delivered
/// from an upstream publisher.
///
/// In the example below, a 1-second `Timer` is used as the data source for an event
/// publisher; the `measureInterval(using:options:)` operator reports the elapsed time
/// between the reception of events on the main run loop:
///
/// cancellable = Timer.publish(every: 1, on: .main, in: .default)
/// .autoconnect()
/// .measureInterval(using: RunLoop.main)
/// .sink { print("\($0)", terminator: "\n") }
///
/// // Prints:
/// // Stride(magnitude: 1.0013610124588013)
/// // Stride(magnitude: 0.9992760419845581)
///
/// The output type of the returned publisher is the time interval of the provided
/// scheduler.
///
/// This operator uses the provided schedulers `now` property to measure intervals
/// between events.
///
/// - Parameters:
/// - scheduler: A scheduler to use for tracking the timing of events.
/// - options: Options that customize the delivery of elements.
/// - Returns: A publisher that emits elements representing the time interval between
/// the elements it receives.
public func measureInterval<Context: Scheduler>(
using scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.MeasureInterval<Self, Context> {
return .init(upstream: self, scheduler: scheduler)
}
}
extension Publishers {
/// A publisher that measures and emits the time interval between events received from
/// an upstream publisher.
public struct MeasureInterval<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Context.SchedulerTimeType.Stride
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The scheduler used for tracking the timing of events.
public let scheduler: Context
/// Creates a publisher that measures and emits the time interval between events
/// received from an upstream publisher.
///
/// - Parameters:
/// - upstream: The publisher from which this publisher receives elements.
/// - scheduler: A scheduler to use for tracking the timing of events.
public init(upstream: Upstream, scheduler: Context) {
self.upstream = upstream
self.scheduler = scheduler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Downstream.Input == Context.SchedulerTimeType.Stride
{
upstream.subscribe(Inner(scheduler: scheduler, downstream: subscriber))
}
}
}
extension Publishers.MeasureInterval {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Context.SchedulerTimeType.Stride,
Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let lock = UnfairLock.allocate()
private let downstream: Downstream
private let scheduler: Context
private var state = SubscriptionStatus.awaitingSubscription
private var last: Context.SchedulerTimeType?
init(scheduler: Context, downstream: Downstream) {
self.downstream = downstream
self.scheduler = scheduler
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscription)
last = scheduler.now
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_: Input) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(subscription) = state,
let previousTime = last else
{
lock.unlock()
return .none
}
let now = scheduler.now
last = now
lock.unlock()
let newDemand = downstream.receive(previousTime.distance(to: now))
if newDemand > 0 {
subscription.request(newDemand)
}
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return
}
state = .terminal
last = nil
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
state = .terminal
last = nil
lock.unlock()
subscription.cancel()
}
var description: String { return "MeasureInterval" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
@@ -5,18 +5,51 @@
// Created by Sergej Jaskiewicz on 14.06.2019.
//
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.
/// This is useful when upstream publishers are doing expensive work you dont want
/// to duplicate, like performing network requests.
///
/// - Parameter createSubject: A closure to create a new Subject each time
/// In contrast with `multicast(subject:)`, this method produces a publisher that
/// creates a separate `Subject` for each subscriber.
///
/// The following example uses a sequence publisher as a counter to publish three
/// random numbers, generated by a `map(_:)` operator.
/// It uses a `multicast(_:)` operator whose closure creates a `PassthroughSubject`
/// to share the same random number to each of two subscribers. Because the multicast
/// publisher is a `ConnectablePublisher`, publishing only begins after a call to
/// `connect()`.
///
/// let pub = ["First", "Second", "Third"].publisher
/// .map( { return ($0, Int.random(in: 0...100)) } )
/// .print("Random")
/// .multicast { PassthroughSubject<(String, Int), Never>() }
///
/// cancellable1 = pub
/// .sink { print ("Stream 1 received: \($0)")}
/// cancellable2 = pub
/// .sink { print ("Stream 2 received: \($0)")}
/// pub.connect()
///
/// // Prints:
/// // Random: receive value: (("First", 9))
/// // Stream 2 received: ("First", 9)
/// // Stream 1 received: ("First", 9)
/// // Random: receive value: (("Second", 46))
/// // Stream 2 received: ("Second", 46)
/// // Stream 1 received: ("Second", 46)
/// // Random: receive value: (("Third", 26))
/// // Stream 2 received: ("Third", 26)
/// // Stream 1 received: ("Third", 26)
///
/// In this example, the output shows that the `print(_:to:)` operator receives each
/// random value only one time, and then sends the value to both subscribers.
///
/// - Parameter createSubject: A closure to create a new `Subject` each time
/// a subscriber attaches to the multicast publisher.
public func multicast<SubjectType: Subject>(
_ createSubject: @escaping () -> SubjectType
@@ -30,8 +63,42 @@ extension Publisher {
///
/// Use a multicast publisher when you have multiple downstream subscribers, but you
/// want upstream publishers to only process one `receive(_:)` call per event.
/// In contrast with `multicast(_:)`, this method produces a publisher shares
/// the provided Subject among all the downstream subscribers.
/// This is useful when upstream publishers are doing expensive work you dont want
/// to duplicate, like performing network requests.
///
/// In contrast with `multicast(_:)`, this method produces a publisher that shares
/// the provided `Subject` among all the downstream subscribers.
///
/// The following example uses a sequence publisher as a counter to publish three
/// random numbers, generated by a `map(_:)` operator.
/// It uses a `multicast(subject:)` operator with a `PassthroughSubject` to share
/// the same random number to each of two subscribers. Because the multicast publisher
/// is a `ConnectablePublisher`, publishing only begins after a call to `connect()`.
///
/// let pub = ["First", "Second", "Third"].publisher
/// .map( { return ($0, Int.random(in: 0...100)) } )
/// .print("Random")
/// .multicast(subject: PassthroughSubject<(String, Int), Never>())
///
/// cancellable1 = pub
/// .sink { print ("Stream 1 received: \($0)")}
/// cancellable2 = pub
/// .sink { print ("Stream 2 received: \($0)")}
/// pub.connect()
///
/// // Prints:
/// // Random: receive value: (("First", 78))
/// // Stream 2 received: ("First", 78)
/// // Stream 1 received: ("First", 78)
/// // Random: receive value: (("Second", 98))
/// // Stream 2 received: ("Second", 98)
/// // Stream 1 received: ("Second", 98)
/// // Random: receive value: (("Third", 61))
/// // Stream 2 received: ("Third", 61)
/// // Stream 1 received: ("Third", 61)
///
/// In this example, the output shows that the `print(_:to:)` operator receives each
/// random value only one time, and then sends the value to both subscribers.
///
/// - Parameter subject: A subject to deliver elements to downstream subscribers.
public func multicast<SubjectType: Subject>(
@@ -46,18 +113,19 @@ extension Publisher {
extension Publishers {
/// A publisher that uses a subject to deliver elements to multiple subscribers.
///
/// Use a multicast publisher when you have multiple downstream subscribers, but you
/// want upstream publishers to only process one `receive(_:)` call per event.
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
: ConnectablePublisher
where Upstream.Failure == SubjectType.Failure,
Upstream.Output == SubjectType.Output
{
// NOTE: This class has been audited for thread safety
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure to create a new Subject each time a subscriber attaches
@@ -81,12 +149,10 @@ extension Publishers {
return subject
}
/// Creates a multicast publisher that applies a closure to create a subject
/// that delivers elements to subscribers.
/// Creates a multicast publisher that applies a closure to create a subject that
/// delivers elements to subscribers.
///
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
/// - Parameter createSubject: A closure to create a new Subject each time
/// - Parameter createSubject: A closure that returns a `Subject` each time
/// a subscriber attaches to the multicast publisher.
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
self.upstream = upstream
@@ -159,6 +225,7 @@ extension Publishers.Multicast {
lock.lock()
guard case let .ready(upstream, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(upstream: upstream,
@@ -0,0 +1,241 @@
//
// Publishers.Output.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
extension Publisher {
/// Republishes elements up to the specified maximum count.
///
/// Use `prefix(_:)` to limit the number of elements republished to the downstream
/// subscriber.
///
/// In the example below, the `prefix(_:)` operator limits its output to the first
/// two elements before finishing normally:
///
/// let numbers = (0...10)
/// cancellable = numbers.publisher
/// .prefix(2)
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1"
///
/// - Parameter maxLength: The maximum number of elements to republish.
/// - Returns: A publisher that publishes up to the specified number of elements
/// before completing.
public func prefix(_ maxLength: Int) -> Publishers.Output<Self> {
return output(in: ..<maxLength)
}
}
extension Publisher {
/// Publishes a specific element, indicated by its index in the sequence of published
/// elements.
///
/// Use `output(at:)` when you need to republish a specific element specified by
/// its position in the stream. If the publisher completes normally or with an error
/// before publishing the specified element, then the publisher doesnt produce any
/// elements.
///
/// In the example below, the array publisher emits the fifth element in the sequence
/// of published elements:
///
/// let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
/// numbers.publisher
/// .output(at: 5)
/// .sink { print("\($0)") }
///
/// // Prints: "6"
///
/// - Parameter index: The index that indicates the element to publish.
/// - Returns: A publisher that publishes a specific indexed element.
public func output(at index: Int) -> Publishers.Output<Self> {
return output(in: index...index)
}
/// Publishes elements specified by their range in the sequence of published elements.
///
/// Use `output(in:)` to republish a range indices you specify in the published
/// stream. After publishing all elements, the publisher finishes normally.
/// If the publisher completes normally or with an error before producing all
/// the elements in the range, it doesnt publish the remaining elements.
///
/// In the example below, an array publisher emits the subset of elements at
/// the indices in the specified range:
///
/// let numbers = [1, 1, 2, 2, 2, 3, 4, 5, 6]
/// numbers.publisher
/// .output(in: (3...5))
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "2 2 3"
///
/// - Parameter range: A range that indicates which elements to publish.
/// - Returns: A publisher that publishes elements specified by a range.
public func output<Range: RangeExpression>(in range: Range) -> Publishers.Output<Self>
where Range.Bound == Int
{
return .init(upstream: self, range: range.relative(to: 0 ..< .max))
}
}
extension Publishers {
/// A publisher that publishes elements specified by a range in the sequence of
/// published elements.
public struct Output<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// The range of elements to publish.
public let range: CountableRange<Int>
/// Creates a publisher that publishes elements specified by a range.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - range: The range of elements to publish.
public init(upstream: Upstream, range: CountableRange<Int>) {
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
precondition(range.upperBound >= 0, "upperBound must not be negative")
self.upstream = upstream
self.range = range
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, range: range))
}
}
}
extension Publishers.Output: Equatable where Upstream: Equatable {}
extension Publishers.Output {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
// NOTE: This class has been audited for thread safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private var status = SubscriptionStatus.awaitingSubscription
private var remainingUntilStart: Int
private var remainingCount: Int
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream, range: CountableRange<Int>) {
self.downstream = downstream
self.remainingUntilStart = range.lowerBound
self.remainingCount = range.count
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
if remainingUntilStart > 0 {
remainingUntilStart -= 1
return .max(1)
}
let newDemand: Subscribers.Demand
if remainingCount > 0 {
remainingCount -= 1
newDemand = downstream.receive(input)
} else {
newDemand = .none
cancelUpstreamAndFinish()
}
return newDemand
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "Output" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
// MARK: - Private
private func cancelUpstreamAndFinish() {
assert(remainingCount == 0)
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
downstream.receive(completion: .finished)
}
}
}
@@ -0,0 +1,197 @@
//
// Publishers.PrefixUntilOutput.swift
//
//
// Created by Sergej Jaskiewicz on 08.11.2020.
//
extension Publisher {
/// Republishes elements until another publisher emits an element.
///
/// After the second publisher publishes an element, the publisher returned by this
/// method finishes.
///
/// - Parameter publisher: A second publisher.
/// - Returns: A publisher that republishes elements until the second publisher
/// publishes an element.
public func prefix<Other: Publisher>(
untilOutputFrom publisher: Other
) -> Publishers.PrefixUntilOutput<Self, Other> {
return .init(upstream: self, other: publisher)
}
}
extension Publishers {
public struct PrefixUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// Another publisher, whose first output causes this publisher to finish.
public let other: Other
public init(upstream: Upstream, other: Other) {
self.upstream = upstream
self.other = other
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
upstream.subscribe(Inner(downstream: subscriber, trigger: other))
}
}
}
extension Publishers.PrefixUntilOutput {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private struct Termination: Subscriber {
let inner: Inner
var combineIdentifier: CombineIdentifier {
return inner.combineIdentifier
}
func receive(subscription: Subscription) {
inner.terminationReceive(subscription: subscription)
}
func receive(_ input: Other.Output) -> Subscribers.Demand {
return inner.terminationReceive(input)
}
func receive(completion: Subscribers.Completion<Other.Failure>) {
inner.terminationReceive(completion: completion)
}
}
private var termination: Termination?
private var prefixState = SubscriptionStatus.awaitingSubscription
private var terminationState = SubscriptionStatus.awaitingSubscription
private var triggered = false
private let lock = UnfairLock.allocate()
private let downstream: Downstream
init(downstream: Downstream, trigger: Other) {
self.downstream = downstream
let termination = Termination(inner: self)
self.termination = termination
trigger.subscribe(termination)
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = prefixState else {
lock.unlock()
subscription.cancel()
return
}
prefixState = triggered ? .terminal : .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = prefixState else {
lock.unlock()
return .none
}
lock.unlock()
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
let prefixState = self.prefixState
let terminationSubscription = terminationState.subscription
self.prefixState = .terminal
terminationState = .terminal
termination = nil
lock.unlock()
terminationSubscription?.cancel()
if case .subscribed = prefixState {
downstream.receive(completion: completion)
}
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = prefixState else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
let prefixSubscription = prefixState.subscription
let terminationSubscription = terminationState.subscription
prefixState = .terminal
terminationState = .terminal
lock.unlock()
prefixSubscription?.cancel()
terminationSubscription?.cancel()
}
// MARK: - Private
private func terminationReceive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = terminationState else {
lock.unlock()
subscription.cancel()
return
}
terminationState = .subscribed(subscription)
lock.unlock()
subscription.request(.max(1))
}
private func terminationReceive(_ input: Other.Output) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = terminationState else {
lock.unlock()
return .none
}
let prefixSubscription = prefixState.subscription
prefixState = .terminal
terminationState = .terminal
termination = nil
triggered = true
lock.unlock()
prefixSubscription?.cancel()
downstream.receive(completion: .finished)
return .none
}
private func terminationReceive(
completion: Subscribers.Completion<Other.Failure>
) {
lock.lock()
terminationState = .terminal
termination = nil
lock.unlock()
}
}
}
@@ -0,0 +1,176 @@
//
// Publishers.PrefixWhile.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
extension Publisher {
/// Republishes elements while a predicate closure indicates publishing should
/// continue.
///
/// Use `prefix(while:)` to emit values while elements from the upstream publishe
/// meet a condition you specify. The publisher finishes when the closure returns
/// `false`.
///
/// In the example below, the `prefix(while:)` operator emits values while the element
/// it receives is less than five:
///
/// let numbers = (0...10)
/// numbers.publisher
/// .prefix { $0 < 5 }
/// .sink { print("\($0)", terminator: " ") }
///
/// // Prints: "0 1 2 3 4"
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value that indicates whether publishing should continue.
/// - Returns: A publisher that passes through elements until the predicate indicates
/// publishing should finish.
public func prefix(
while predicate: @escaping (Output) -> Bool
) -> Publishers.PrefixWhile<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Republishes elements while an error-throwing predicate closure indicates
/// publishing should continue.
///
/// Use `tryPrefix(while:)` to emit values from the upstream publisher that meet
/// a condition you specify in an error-throwing closure.
/// The publisher finishes when the closure returns `false`. If the closure throws
/// an error, the publisher fails with that error.
///
/// struct OutOfRangeError: Error {}
///
/// let numbers = (0...10).reversed()
/// cancellable = numbers.publisher
/// .tryPrefix {
/// guard $0 != 0 else { throw OutOfRangeError() }
/// return $0 <= numbers.max()!
/// }
/// .sink(
/// receiveCompletion: { print ("completion: \($0)", terminator: " ") },
/// receiveValue: { print ("\($0)", terminator: " ") }
/// )
///
/// // Prints: "10 9 8 7 6 5 4 3 2 1 completion: failure(OutOfRangeError()) "
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether publishing should continue.
/// - Returns: A publisher that passes through elements until the predicate throws or
/// indicates publishing should finish.
public func tryPrefix(
while predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryPrefixWhile<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that republishes elements while a predicate closure indicates
/// publishing should continue.
public struct PrefixWhile<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether whether publishing should continue.
public let predicate: (Upstream.Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
}
}
/// A publisher that republishes elements while an error-throwing predicate closure
/// indicates publishing should continue.
public struct TryPrefixWhile<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether publishing should continue.
public let predicate: (Upstream.Output) throws -> Bool
public init(upstream: Upstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
}
}
}
extension Publishers.PrefixWhile {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
override func receive(
newValue: Input
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
return filter(newValue) ? .continue(newValue) : .finished
}
override var description: String { return "PrefixWhile" }
}
}
extension Publishers.TryPrefixWhile {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
override func receive(
newValue: Input
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
do {
return try filter(newValue) ? .continue(newValue) : .finished
} catch {
return .failure(error)
}
}
override var description: String { return "TryPrefixWhile" }
}
}
@@ -5,7 +5,37 @@
// Created by Sergej Jaskiewicz on 16.06.2019.
//
import COpenCombineHelpers
extension Publisher {
/// Prints log messages for all publishing events.
///
/// Use `print(_:to:)` to log messages the console.
///
/// In the example below, log messages are printed on the console:
///
/// let integers = (1...2)
/// cancellable = integers.publisher
/// .print("Logged a message", to: nil)
/// .sink { _ in }
///
/// // Prints:
/// // Logged a message: receive subscription: (1..<2)
/// // Logged a message: request unlimited
/// // Logged a message: receive value: (1)
/// // Logged a message: receive finished
///
/// - Parameters:
/// - prefix: A string which defaults to empty with which to prefix all log
/// messages.
/// - stream: A stream for text output that receives messages, and which directs
/// output to the console by default. A custom stream can be used to log messages
/// to other destinations.
/// - Returns: A publisher that prints log messages for all publishing events.
public func print(_ prefix: String = "",
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
return .init(upstream: self, prefix: prefix, to: stream)
}
}
extension Publishers {
@@ -13,11 +43,12 @@ extension Publishers {
/// prefixed with a given string.
///
/// This publisher prints log messages when receiving the following events:
/// * subscription
/// * value
/// * normal completion
/// * failure
/// * cancellation
///
/// - subscription
/// - value
/// - normal completion
/// - failure
/// - cancellation
public struct Print<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
@@ -54,24 +85,12 @@ extension Publishers {
}
}
extension Publisher {
/// Prints log messages for all publishing events.
///
/// - Parameter prefix: A string with which to prefix all log messages. Defaults to
/// an empty string.
/// - Returns: A publisher that prints log messages for all publishing events.
public func print(_ prefix: String = "",
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
return Publishers.Print(upstream: self, prefix: prefix, to: stream)
}
}
extension Publishers.Print {
private final class Inner<Downstream: Subscriber>: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Downstream.Input
typealias Failure = Downstream.Failure
@@ -89,7 +108,7 @@ extension Publishers.Print {
private var downstream: Downstream
private let prefix: String
private var stream: PrintTarget?
private var subscription: Subscription?
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
@@ -104,9 +123,14 @@ extension Publishers.Print {
func receive(subscription: Subscription) {
log("\(prefix)receive subscription: (\(subscription))")
lock.do {
self.subscription = subscription
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
@@ -130,6 +154,9 @@ extension Publishers.Print {
case .failure(let error):
log("\(prefix)receive error: (\(error))")
}
lock.lock()
status = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
@@ -139,19 +166,35 @@ extension Publishers.Print {
} else {
log("\(prefix)request unlimited")
}
subscription?.request(demand)
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
log("\(prefix)receive cancel")
subscription?.cancel()
subscription = nil
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "Print" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
// MARK: - Private
private func log(_ text: String) {
if var stream = stream {
Swift.print(text, to: &stream)
@@ -0,0 +1,227 @@
//
// Publishers.ReceiveOn.swift
//
//
// Created by Sergej Jaskiewicz on 02.12.2019.
//
extension Publisher {
/// Specifies the scheduler on which to receive elements from the publisher.
///
/// You use the `receive(on:options:)` operator to receive results and completion on
/// a specific scheduler, such as performing UI work on the main run loop. In contrast
/// with `subscribe(on:options:)`, which affects upstream messages,
/// `receive(on:options:)` changes the execution context of downstream messages.
///
/// In the following example, the `subscribe(on:options:)` operator causes
/// `jsonPublisher` to receive requests on `backgroundQueue`, while
/// the `receive(on:options:)` causes `labelUpdater` to receive elements and
/// completion on `RunLoop.main`.
///
/// // Some publisher.
/// let jsonPublisher = MyJSONLoaderPublisher()
///
/// // Some subscriber that updates the UI.
/// let labelUpdater = MyLabelUpdateSubscriber()
///
/// jsonPublisher
/// .subscribe(on: backgroundQueue)
/// .receive(on: RunLoop.main)
/// .subscribe(labelUpdater)
///
///
/// Prefer `receive(on:options:)` over explicit use of dispatch queues when performing
/// work in subscribers. For example, instead of the following pattern:
///
/// pub.sink {
/// DispatchQueue.main.async {
/// // Do something.
/// }
/// }
///
/// Use this pattern instead:
///
/// pub.receive(on: DispatchQueue.main).sink {
/// // Do something.
/// }
///
/// > Note: `receive(on:options:)` doesnt affect the scheduler used to cal
/// the subscribers `receive(subscription:)` method.
///
/// - Parameters:
/// - scheduler: The scheduler the publisher uses for element delivery.
/// - options: Scheduler options used to customize element delivery.
/// - Returns: A publisher that delivers elements using the specified scheduler.
public func receive<Context: Scheduler>(
on scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.ReceiveOn<Self, Context> {
return .init(upstream: self, scheduler: scheduler, options: options)
}
}
extension Publishers {
/// A publisher that delivers elements to its downstream subscriber on a specific
/// scheduler.
public struct ReceiveOn<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The scheduler the publisher is to use for element delivery.
public let scheduler: Context
/// Scheduler options that customize the delivery of elements.
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.upstream = upstream
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(scheduler: scheduler,
options: options,
downstream: subscriber)
upstream.subscribe(inner)
}
}
}
extension Publishers.ReceiveOn {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let lock = UnfairLock.allocate()
private let downstream: Downstream
private let scheduler: Context
private let options: Context.SchedulerOptions?
private var state = SubscriptionStatus.awaitingSubscription
private let downstreamLock = UnfairRecursiveLock.allocate()
init(scheduler: Context,
options: Context.SchedulerOptions?,
downstream: Downstream) {
self.downstream = downstream
self.scheduler = scheduler
self.options = options
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscription)
lock.unlock()
downstreamLock.lock()
downstream.receive(subscription: self)
downstreamLock.unlock()
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return .none
}
lock.unlock()
scheduler.schedule(options: options) {
self.scheduledReceive(input)
}
return .none
}
private func scheduledReceive(_ input: Input) {
lock.lock()
guard state.subscription != nil else {
lock.unlock()
return
}
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
if newDemand == .none { return }
lock.lock()
let subscription = state.subscription
lock.unlock()
subscription?.request(newDemand)
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
state = .pendingTerminal(subscription)
lock.unlock()
scheduler.schedule(options: options) {
self.scheduledReceive(completion: completion)
}
}
private func scheduledReceive(completion: Subscribers.Completion<Failure>) {
lock.lock()
state = .terminal
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "ReceiveOn" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}

Some files were not shown because too many files have changed in this diff Show More