172 Commits

Author SHA1 Message Date
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
Sergej Jaskiewicz 46007658a1 Add missing Equatable comformances
- Collect
- Contains
- Count
- Drop
- Last
2019-10-17 14:40:56 +03:00
Sergej Jaskiewicz c4c7f2172d Add more tests for Scan, TryScan 2019-10-17 14:40:56 +03:00
Eric Patey 5b0a21a0b9 Implement Publishers.Scan 2019-10-17 14:40:56 +03:00
Sergej Jaskiewicz f4e191b2ff Add more tests for Publishers.Drop (#82) 2019-10-17 09:41:53 +03:00
Sven c275e51cdc Implement Publishers.Drop (#70) 2019-10-16 13:26:10 +03:00
Sergej Jaskiewicz a84105133c Increase timeout for DispatchQueueSchedulerTests.testScheduleActionOnceNow 2019-10-16 02:10:47 +03:00
Sergej Jaskiewicz ef3ebd965a Extract locking API into COpenCombineHelpers module 2019-10-16 02:10:47 +03:00
Sergej Jaskiewicz a08b99c886 Fix a compiler crash on Linux
This crash could only be reproduced in release configuration
2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz 3398499540 Remove Unreachable.swift 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz 1bf193ddaa Bump Swift version on Linux CI 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz 3a88dfd76b Implemented Comparison. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz bd0b69d7cb Implement Collect. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz dba76c3c41 Implement Contains, ContainsWhere, TryContainsWhere. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz 5863492753 Implement AllSatisfy, TryAllSatisfy. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Joe Spadafora 2f2e16ee1f Implement Last, LastWhere, TryLastWhere. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz bca131c2a4 Simplify Publishers.Count. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz e999fafdce Simplify First, FirstWhere, TryFirstWhere. Use ReduceProducer. 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz b38830e0f1 Implement Reduce, TryReduce. Use ReduceProducer 2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz 525405f64d Implement ReduceProducer
ReduceProducer is a helper class that makes implementing reduce-like
operators trivial.
2019-10-15 20:51:44 +03:00
Sergej Jaskiewicz 693d1145f8 Reenable compatibility tests (#79) 2019-10-13 20:22:03 +03:00
Sergej Jaskiewicz 2f9ddc2229 Bump Swift version on Travis for Linux (#78) 2019-10-10 10:52:18 +03:00
Sergej Jaskiewicz bcd1b727f8 Fix SwiftLint 2019-10-09 23:29:50 +03:00
Sergej Jaskiewicz 5d1034fcc0 Fix Linux build failure due to PTHREAD_MUTEX_ERRORCHECK there being Int, not Int32 2019-10-09 23:03:35 +03:00
Sergej Jaskiewicz 2378f3d97e Better error handling for pthread calls 2019-10-09 20:08:24 +03:00
Sergej Jaskiewicz 4a965830e7 Update docs to match Xcode 11.1 (#77) 2019-10-09 17:51:52 +03:00
Sergej Jaskiewicz 9eabadb7c9 Mention GYB in README.md 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz dcfaec2c9d Add tests for Publishers.MapKeyPath 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz 219ee38119 Update RemainingCombineInterface.swift 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz 3a5389d398 GYB cleanup 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz 69ead1c8fb Fix indentation 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz 8e6404592e Add .gitattributes 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz 14b7ced2fe Use gyb tool to implement MapKeyPath 2019-10-09 00:12:07 +03:00
Sergej Jaskiewicz d7b9e87f6d Execute tests in parallel 2019-10-08 14:26:09 +03:00
Sergej Jaskiewicz 4fd04b8a00 Test Publishers.Print for printing to stdout 2019-10-08 14:26:09 +03:00
Sergej Jaskiewicz 5f92ee05d2 Fix the semantics to be compatible with Xcode 11.1 (#74) 2019-10-08 11:20:42 +03:00
Sven bdd703abb3 Fix Sequence cancellation (#73)
* Add test to cancel after first value received from Sequence publisher

* Stop sending values from sequence if cancelled
2019-10-07 23:19:05 +03:00
Sergej Jaskiewicz e41c48a5cd Use UInt64 as CombineIdentifier (instead of UInt) 2019-10-03 15:37:44 +03:00
Sergej Jaskiewicz df0b8b08db Add tests for Publishers.Share 2019-10-03 15:22:18 +03:00
Sergej Jaskiewicz 7056143b99 Add tests for Publishers.Autoconnect 2019-10-03 15:22:18 +03:00
Sergej Jaskiewicz 0a965ba60a Adopt new locking API 2019-10-03 15:22:18 +03:00
Sergej Jaskiewicz 7dfaa4edea Implement Publishers.Share 2019-10-03 15:22:18 +03:00
Sergej Jaskiewicz 3e8f2774a4 Implement Publishers.Autoconnect 2019-10-03 15:22:18 +03:00
Sergej Jaskiewicz 68e9bbe164 Extract generation of a next CombineIdentifier to COpenCombineHelpers (#69) 2019-10-02 23:28:09 +03:00
Sergej Jaskiewicz 0f71c33d72 Add header guards 2019-09-23 16:21:08 +03:00
Sergej Jaskiewicz 3f61648f82 Use CInt instead of Int32 2019-09-23 16:21:08 +03:00
Sergej Jaskiewicz c621ceb267 Rename OpenCombineAtomics -> COpenCombineAtomics 2019-09-23 16:21:08 +03:00
Sergej Jaskiewicz 2aa297ec39 Fix access race detected by TSan 2019-09-23 16:21:08 +03:00
Sergej Jaskiewicz 9cb27bb91b Better Locking internal API 2019-09-23 16:21:08 +03:00
Sergej Jaskiewicz d74f68da86 Audit Subscribers.Assign for thread safety (nothing to do here) 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz f68dcd520f Audit Subscribers.Sink for thread safety (nothing to do here) 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz 432fd4f48f Audit Publishers.ReplaceError for thread safety 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz 9c6bbda0c4 Make Publishers.MapError.Inner a struct 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz 3990ec2afb Audit Publishers.Sequence for thread safety 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz 39dd9e40bf Add reflection test for Publishers.ReplaceNil 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz fd7c0459b9 Make Publishers.SetFailureType.Inner a struct 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz f7145e7fa5 Fix TryMap compatibility tests 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz ecd4766129 Audit Optional.Publisher for thread safety (nothing to do here) 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz e00a6f06fc Audit Optional.Publisher for thread safety (nothing to do here) 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz 23ee3a4b7b Audit Just for thread safety (nothing to do there) 2019-09-22 23:10:54 +03:00
Sergej Jaskiewicz 9c913124eb Audit TryMap for thread safety, fix its semantics (#64) 2019-09-21 22:09:48 +03:00
Sergej Jaskiewicz 7ddd15b334 Audit Publishers.Multicast for thread safety (#63) 2019-09-20 16:15:35 +03:00
Sergej Jaskiewicz 72753ef93c Implement Publishers.MakeConnectable (#61)
* Implement Publishers.MakeConnectable

* Add MakeConnectable tests
2019-09-19 14:06:57 +03:00
Sergej Jaskiewicz 816426b48c Fix iterator.next() being called twice in Publishers.Sequence 2019-09-19 05:10:34 +03:00
Sergej Jaskiewicz 47fb390081 Add eraseToAnyPublisher() method 2019-09-18 16:55:48 +03:00
Sergej Jaskiewicz 1d3327f6bf Revert "Remove XCTUnwrap implementation"
This reverts commit 16690e6f1f.
2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz eb7478d430 Add Unreachable optimizer hint 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz f69621f0e2 Remove XCTUnwrap implementation 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz 7f3cccf1ae Audit SubjectSubscriber for thread-safety 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz ec037dbb3d Fix semantics of Publishers.Print 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz 8a39f35d3f Simplify Publishers.Multicast.connect() method 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz 7fb92bffc6 Replace SubscriberType with Downstream in generic params 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz e441ea3048 Add missing Equatable conformances for First, ReplaceError 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz 22f7b6d10d Fix code style issues with FlatMap 2019-09-16 19:13:24 +03:00
Sergej Jaskiewicz 7431d21c9c Increase timeouts for DispatchQueueSchedulerTests 2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 1d901fca7f Remove flaky test for DispatchQueue scheduler 2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 9834eab0ea Remove some nasty unsafe code from tests
https://twitter.com/UINT_MIN/status/1168581618753163264
2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 1ce9660ce9 Remove .swiftpm 2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 313d6befa6 Fix a data race in DispatchQueueSchedulerTests
Also gitignore .swiftpm and make SwiftLint happy
2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 8c7f061892 Fully cover DispatchQueue extension with tests 2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 2ac2470579 Initial implementation of DispatchQueue scheduler 2019-09-15 16:07:52 +03:00
Sergej Jaskiewicz 57c9ae8590 Initial implementation of DispatchQueue scheduler 2019-09-15 16:07:52 +03:00
Eric Patey d57c878651 FlatMap (#45)
Implement FlatMap
2019-09-13 10:57:17 -04:00
Eric Patey 7fa91778c2 Fix failing tests caused by Apple changes in GM Seed. (#56)
Update tests and implementation to match Apple changes in gm seed.
2019-09-13 10:50:38 -04:00
Sergej Jaskiewicz d15e604764 Remove mention of XCTestManifests from the Dangerfile 2019-09-13 14:45:28 +03:00
Evgeniy 07c7a98d72 @propertyWrapper Published (#52) 2019-09-07 18:10:53 +03:00
Sergej Jaskiewicz 01ef05be1f Pass --enable-index-store flag when testing in release mode 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz beee9d0d51 Remove allTests properties in test classes 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz aacd1a326c Remove LinuxMain.swift 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz 5528adcc67 Enable test discovery on Linux 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz 1b810d0536 Update Swift version on Linux 2019-09-07 16:32:34 +03:00
Bogdan Vlad 8b25238154 ReplaceError implementation (#50) 2019-09-01 22:05:44 +03:00
Sergej Jaskiewicz 9b9915bde7 Fix Optional.Publisher.collect() operator specialization
Update for Xcode 11.0 beta 6
2019-08-26 03:51:12 +03:00
Eric Patey 27f01e5f21 Implement IgnoreOutput (#44) 2019-08-20 17:32:33 +03:00
Sergej Jaskiewicz 739eb47409 Update for Xcode 11 beta 6
Yes, it's the only change.
2019-08-20 10:54:17 +03:00
Sergej Jaskiewicz 14d5a90e89 Add Slack badge 2019-08-05 19:57:58 +03:00
Sergej Jaskiewicz 0e869bc861 Implement CompactMap (#32) 2019-08-02 18:59:53 +03:00
Joe Spadafora 2f38069166 First where (#29) 2019-08-02 18:55:51 +03:00
Sergej Jaskiewicz 97d07d0a14 Better linting for inheritance clauses and dictionary literals 2019-08-02 14:18:17 +03:00
Joe Spadafora d3888a3808 Implement Filter/TryFilter (#22)
* Adds filter and try filter implementations

* Implement Filter

* Remove @testable declaration

* Fix linting

* Updates tests and creates testing helper

* Fix allTests to include all tests

* Renames TestHelper to OperatorTestHelper and adds documentation

* Adds more test coverage

* Updates to use subclasses for filter / tryfilter

* Adds subscription test

* Fix subscriber demand to be lazy

* Fix CustomPublisherBase changes from master

* Fix iOS availability on test helper

* Updates availability for test functions

* Simplify Filter implementation, add more tests

* Ensure test suite consistency on Darwin and Linux

* Add missing tests to XCTestManifests.swift
2019-08-02 00:20:35 +03:00
Franz Busch d2b8709afb Store newly send value in internal variable inside CurrentValueObject (#39) 2019-08-01 23:34:27 +03:00
Sergej Jaskiewicz a28177e9c5 Cache homebrew artifacts 2019-08-01 15:49:00 +03:00
Sergej Jaskiewicz cef19fce4b Use Danger CI 2019-08-01 15:49:00 +03:00
Sergej Jaskiewicz 7f6bba62de Add .hound.yml 2019-08-01 11:52:46 +03:00
Sergej Jaskiewicz 26faf90356 Fix assertCrashes(within:) for Linux 2019-07-31 23:49:17 +03:00
Sergej Jaskiewicz 511d676c30 Test send(subscription:) for PassthroughSubject & CurrentValueSubject 2019-07-31 23:49:17 +03:00
Sergej Jaskiewicz f6ecc28d25 Update for Xcode 11.0 beta 5 2019-07-31 23:49:17 +03:00
Sergej Jaskiewicz 03fe398395 Update for Xcode 11.0 beta 4 (#36)
* Update for Xcode 11.0 beta 4

* Temporarily disable compatibility tests on Travis CI

* Fix SwiftLint warnings

* More test coverage
2019-07-26 16:50:40 +03:00
Sergej Jaskiewicz 47bbd8d81a Update .travis.yml for Xcode 11 (#33)
* Update .travis.yml for Xcode 11

* Enable compatibility testing on iOS
2019-07-11 05:58:53 +03:00
Sergej Jaskiewicz 5e3a18d8c7 Implement Publishers.SetFailureType (#28) 2019-07-11 00:54:07 +03:00
Sergej Jaskiewicz 74e1c1ae32 Actualize XCTestManifests.swift 2019-07-08 18:20:23 +03:00
Sergej Jaskiewicz 4dbc8cc09b Implement Publisher.subscribe<S: Subject>(S) (#27) 2019-07-08 18:07:47 +03:00
Joe Spadafora 2849b1c195 Adds Publishers.Deferred and tests (#26) 2019-07-08 17:30:29 +03:00
Joe Spadafora c3e6905c68 Adds count implementation and tests (#20) 2019-07-08 13:05:16 +03:00
Joe Spadafora e75ad6b0de Implements replaceNil function (#24) 2019-07-05 23:28:04 +03:00
Sergej Jaskiewicz c790e5f708 Fix Multicast semantics and lots of other stuff (#25)
* Fix Multicast semantics and lots of other stuff

* Fix MapError subscription semantics

* Fix subscriptions semantics for exhausted CurrentValueSubject
2019-07-05 08:27:25 +03:00
Joe Spadafora 065b981934 Add map error (#23) 2019-07-05 07:28:37 +03:00
Sergej Jaskiewicz fbd1fd7014 Don't use CODECOV_TOKEN 2019-07-04 20:56:34 +03:00
199 changed files with 30430 additions and 7721 deletions
+268
View File
@@ -0,0 +1,268 @@
version: 2
jobs:
"Execute tests on macOS 10.15.0 (Xcode 11.2.0, Swift 5.1.2)":
macos:
xcode: "11.2.0"
environment:
SWIFT_VERSION: "5.1.2"
steps:
- checkout
- run:
name: Building and running tests in debug mode with coverage
command: |
make test-debug \
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_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
- run:
name: Building and running tests in release mode
command: |
make test-release \
SWIFT_TEST_FLAGS="--build-path .build-test-release"
- run:
name: Generating Xcode project
command: make generate-xcodeproj
- run:
name: Building for testing on macOS 10.15.0 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-sdk macosx10.15 \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing on macOS 10.15.0 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-sdk macosx10.15 \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash) -D DerivedData
"Execute compatibility tests on iOS 13.2.2 (Xcode 11.2.0, Swift 5.1.2)":
macos:
xcode: "11.2.0"
environment:
SWIFT_VERSION: "5.1.2"
steps:
- checkout
- run:
name: Generating Xcode project
command: make generate-compatibility-xcodeproj
- run:
name: Building for testing on iOS 13.2.2 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.2.2" \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing against Combine on iOS 13.2.2 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.2.2" \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
"Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)":
macos:
xcode: "10.2.1"
environment:
BUNDLE_PATH: .bundle # path to install gems and use for caching
SWIFT_VERSION: "5.0.1"
steps:
- checkout
- run:
name: Installing gem dependencies
command: bundle install && bundle clean
- restore_cache:
keys:
- v1-simulator-cache-{{ arch }}
- run:
# CircleCI doesn't have an iOS 9 simulator, so we need to install it manually.
name: Installing iOS 9 simulator
command: |
bundle exec xcversion simulators --install="iOS 9.3"
bundle exec xcversion simulators
xcrun simctl list
- save_cache:
key: v1-simulator-cache-{{ arch }}
paths:
- ~/Library/Caches/XcodeInstall
- run:
name: Generating Xcode project
command: |
make generate-xcodeproj
xcodebuild -scheme OpenCombine-Package -showdestinations
- run:
name: Building for testing on iOS 9.3 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing on iOS 9.3 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash) -D DerivedData
"Execute tests on Ubuntu 18.04 (Swift 5.1.1)":
docker:
- image: swift:5.1.1-bionic
environment:
SWIFT_VERSION: "5.1.1"
steps:
- checkout
- run:
name: Installing dependencies
command: |
apt update -y
apt upgrade -y
apt install -y curl
- run:
name: Building and running tests in debug mode with coverage
command: | # We need to run the test command twice because of https://bugs.swift.org/browse/SR-10783
make test-debug \
SWIFT_TEST_FLAGS="--enable-test-discovery \
--enable-index-store \
--enable-code-coverage \
--build-path .build-test-debug" \
> /dev/null 2>&1 \
|| true
make test-debug \
SWIFT_TEST_FLAGS="--enable-test-discovery \
--enable-index-store \
--enable-code-coverage \
--build-path .build-test-debug"
llvm-cov show \
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
.build-test-debug/debug/OpenCombinePackageTests.xctest \
> coverage.txt
- run:
name: Building and running tests in debug mode with TSan
command: | # We need to run the test command twice because of https://bugs.swift.org/browse/SR-10783
make test-debug-sanitize-thread \
SWIFT_TEST_FLAGS="--enable-test-discovery \
--enable-index-store \
--build-path .build-test-debug-sanitize-thread" \
> /dev/null 2>&1 \
|| true
make test-debug-sanitize-thread \
SWIFT_TEST_FLAGS="--enable-test-discovery \
--enable-index-store \
--build-path .build-test-debug-sanitize-thread" \
- run:
name: Building and running tests in release mode
command: |
make test-release \
SWIFT_TEST_FLAGS="--enable-test-discovery \
--enable-index-store \
--build-path .build-test-release"
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash)
"Run SwiftLint and Danger":
macos:
xcode: "11.2.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.2.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.2.0, Swift 5.1.2)"
"OpenCombine: execute compatibility tests":
jobs:
- "Execute compatibility tests on iOS 13.2.2 (Xcode 11.2.0, Swift 5.1.2)"
"OpenCombine: execute tests on iOS":
jobs:
- "Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)"
"OpenCombine: execute tests on Linux":
jobs:
- "Execute tests on Ubuntu 18.04 (Swift 5.1.1)"
"OpenCombine: run SwiftLint and Danger":
jobs:
- "Run SwiftLint and Danger"
"OpenCombine: validate podspec files":
jobs:
- "Run Pod spec lint"
+3
View File
@@ -0,0 +1,3 @@
*.swift.gyb linguist-language=Swift
**/GENERATED-* linguist-generated=true
+114
View File
@@ -2,6 +2,7 @@
/.build
/Packages
/*.xcodeproj
/.swiftpm
# Created by https://www.gitignore.io/api/Xcode
# Edit at https://www.gitignore.io/?templates=Xcode
@@ -42,3 +43,116 @@ DerivedData/
# End of https://www.gitignore.io/api/Xcode
.idea
# Created by https://www.gitignore.io/api/Python
# Edit at https://www.gitignore.io/?templates=Python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# End of https://www.gitignore.io/api/Python
.bundle/
node_modules/
+34 -7
View File
@@ -16,6 +16,8 @@ disabled_rules:
- identifier_name
- nesting
- notification_center_detachment
- no_fallthrough_only
- no_space_in_method_call
- redundant_string_enum_value
- todo
- trailing_comma
@@ -59,7 +61,6 @@ opt_in_rules:
- unneeded_parentheses_in_closure_argument
- untyped_error_in_catch
- unused_import
- unused_private_declaration
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- yoda_condition
@@ -72,12 +73,38 @@ line_length:
generic_type_name:
min_length: 3
file_types_order:
order:
- main_type
- extension
- supporting_type
attributes:
always_on_line_above:
- "@usableFromInline"
custom_rules:
no_foundation_dependency:
included: Sources/OpenCombine/
name: "No Foundation Dependency"
regex: "^import.*Foundation.*$"
message: "We don't want to depend on Foundation"
severity: error
no_dispatch_dependency:
included: Sources/OpenCombine/
name: "No Dispatch Dependency"
regex: "^import.*Dispatch.*$"
message: "We don't want to depend on Dispatch"
severity: error
inheritance_colon:
name: "Inheritance Colon"
regex: '\s[A-Z_]\w*(<[\w\s:\.,]+>)?(?: +:\s*|:(?:\s{0}|\s{2,}))([\[|\(]*\S)'
message: "Colons should be next to the identifier of the inheriting type"
severity: warning
match_kinds:
- identifier
- typeidentifier
dictionary_type_colon:
name: "Dictionary Type Colon"
regex: '\[\w+(?:(?:\s{0}|\s{2,}):| :(?:\s{0}|\s{2,})\w)'
message: "Colon should be surrounded by a single whitespace in a dictionary literal"
severity: warning
match_kinds:
- typeidentifier
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,102 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OpenCombine"
BuildableName = "OpenCombine"
BlueprintName = "OpenCombine"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OpenCombineTests"
BuildableName = "OpenCombineTests"
BlueprintName = "OpenCombineTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
enableThreadSanitizer = "YES"
codeCoverageEnabled = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OpenCombine"
BuildableName = "OpenCombine"
BlueprintName = "OpenCombine"
ReferencedContainer = "container:">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OpenCombineTests"
BuildableName = "OpenCombineTests"
BlueprintName = "OpenCombineTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OpenCombine"
BuildableName = "OpenCombine"
BlueprintName = "OpenCombine"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
-61
View File
@@ -1,61 +0,0 @@
language: generic
addons:
homebrew:
packages:
- swiftlint
update: true
matrix:
include:
- name: "Ubuntu 16.04 | Swift 5.1 | Tests"
os: linux
dist: xenial
sudo: required
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
- name: "macOS 10.14 | Swift 5.0 | Tests"
os: osx
osx_image: xcode10.2
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES"
# - name: "macOS 10.14 | Swift 5.1"
# os: osx
# osx_image: xcode10.2
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
# - name: "macOS 10.15 | Swift 5.1"
# os: osx
# osx_image: xcode11.0
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_COMPATIBILITY_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | SwiftLint"
os: osx
osx_image: xcode10.2
env: SWIFT_VERSION="5.0" SWIFT_LINT="YES"
before_install:
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
cat /proc/cpuinfo;
fi
install:
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
fi
- if [[ $CODE_COVERAGE == "YES" ]]; then
gem install xcpretty;
fi
script:
- if [[ $SWIFT_LINT != "YES" ]]; then
swift test -c debug --enable-code-coverage --sanitize thread;
fi
- if [[ $SWIFT_LINT != "YES" ]]; then
swift test -c release;
fi
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
swift test -c release -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST;
fi
- if [[ $SWIFT_LINT == "YES" ]]; then
swiftlint lint --strict --reporter "emoji";
fi
after_success:
- if [[ $CODE_COVERAGE == "YES" ]]; then
swift package generate-xcodeproj --enable-code-coverage;
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN;
fi
+1
View File
@@ -0,0 +1 @@
OTHER_SWIFT_FLAGS = $(inherited) -DOPENCOMBINE_COMPATIBILITY_TEST
+76
View File
@@ -0,0 +1,76 @@
import Danger
import Foundation
extension StringProtocol {
func dropSuffix<S: StringProtocol>(_ suffix: S) -> SubSequence {
if hasSuffix(suffix) {
return self[..<index(endIndex, offsetBy: -suffix.count)]
} else {
return self[...]
}
}
func directoryAndFileName() -> (SubSequence, SubSequence) {
let lastPathSeparator = lastIndex(of: "/")
if let lastPathSeparator = lastPathSeparator {
return (self[..<lastPathSeparator], self[index(after: lastPathSeparator)...])
} else {
return (".", self[...])
}
}
}
let danger = Danger()
let allCreatedAndModified = danger.git.createdFiles + danger.git.modifiedFiles
do {
// Fail if the committer modified a GYB template but forgot to run `make gyb`.
let modifiedTemplates = allCreatedAndModified.filter { $0.hasSuffix(".gyb") }
for modifiedTemplate in modifiedTemplates {
let (directory, filename) = modifiedTemplate.directoryAndFileName()
let generated = "\(directory)/GENERATED-\(filename.dropSuffix(".gyb"))"
if !allCreatedAndModified.contains(generated) {
fail("""
A template \(modifiedTemplate) was modified, but the file \(generated) \
was not regenerated.
Run `make gyb` from the root of the project and commit the changes.
""")
}
}
}
do {
// Fail if the committer modified a generated file.
// A template should be modified instead.
for modifiedGeneratedFile in danger.git.modifiedFiles
where modifiedGeneratedFile.contains("GENERATED-")
{
let template = modifiedGeneratedFile
.replacingOccurrences(of: "GENERATED-", with: "") + ".gyb"
if !danger.git.modifiedFiles.contains(template) {
fail("""
A generated file \(modifiedGeneratedFile) was modified, but \
the template it was generated from was not modified.
Please modify the template \(template) instead, \
run `make gyb` from the root of the project and commit the changes.
""")
}
}
}
SwiftLint.lint(inline: true,
configFile: ".swiftlint.yml",
strict: true,
lintAllFiles: true)
if danger.warnings.isEmpty, danger.fails.isEmpty {
markdown("LGTM")
}
+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.68.0)
faraday (0.17.0)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.134.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.23.9)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.3.2)
google-cloud-env (~> 1.0)
google-cloud-env (1.2.1)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
google-api-client (~> 0.23)
google-cloud-core (~> 1.2)
googleauth (>= 0.6.2, < 0.10.0)
googleauth (0.6.7)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
json (2.2.0)
jwt (2.1.0)
memoist (0.16.1)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mini_magick (4.9.5)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.3.0)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcode-install (2.6.2)
claide (>= 0.9.1, < 1.1.0)
fastlane (>= 2.1.0, < 3.0.0)
xcodeproj (1.13.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
xcode-install
BUNDLED WITH
2.0.1
+47
View File
@@ -0,0 +1,47 @@
SWIFT_EXE=swift
SWIFT_TEST_FLAGS=
SWIFT_BUILD_FLAGS=-Xcc -Wunguarded-availability
debug:
$(SWIFT_EXE) build -c debug $(SWIFT_BUILD_FLAGS)
release:
$(SWIFT_EXE) build -c release $(SWIFT_BUILD_FLAGS)
test-debug:
$(SWIFT_EXE) test -c debug $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
test-debug-sanitize-thread:
$(SWIFT_EXE) test -c debug --sanitize thread $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
test-release:
$(SWIFT_EXE) test -c release $(SWIFT_BUILD_FLAGS) $(SWIFT_TEST_FLAGS)
swift-version:
$(SWIFT_EXE) -version
test-compatibility:
$(SWIFT_EXE) test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
generate-compatibility-xcodeproj:
$(SWIFT_EXE) package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
open OpenCombine.xcodeproj
generate-xcodeproj:
$(SWIFT_EXE) package $(SWIFT_BUILD_FLAGS) generate-xcodeproj --enable-code-coverage
gyb:
$(shell ./utils/recursively_gyb.sh)
clean:
rm -rf .build
.PHONY: debug release \
test-debug \
test-release \
swift-version \
test-compatibility-debug \
generate-compatibility-xcodeproj \
generate-xcodeproj \
gyb \
clean
+27
View File
@@ -0,0 +1,27 @@
Pod::Spec.new do |spec|
spec.name = "OpenCombine"
spec.version = "0.7.0"
spec.summary = "Open source implementation of Apple's Combine framework for processing values over time."
spec.description = <<-DESC
An open source implementation of Apple's Combine framework for processing values over time.
DESC
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
spec.license = "MIT"
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
spec.swift_version = "5.0"
spec.osx.deployment_target = "10.10"
spec.ios.deployment_target = "8.0"
spec.watchos.deployment_target = "2.0"
spec.tvos.deployment_target = "9.0"
spec.source_files = "Sources/COpenCombineHelpers/**/*.{h,cpp}", "Sources/OpenCombine/**/*.swift"
spec.public_header_files = "Sources/COpenCombineHelpers/include/*.h"
spec.libraries = "c++"
end
+25
View File
@@ -0,0 +1,25 @@
Pod::Spec.new do |spec|
spec.name = "OpenCombineDispatch"
spec.version = "0.7.0"
spec.summary = "OpenCombine + Dispatch interoperability"
spec.description = <<-DESC
Extends `DispatchQueue` with conformance to the `Scheduler` protocol
DESC
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
spec.license = "MIT"
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
spec.swift_version = "5.0"
spec.osx.deployment_target = "10.10"
spec.ios.deployment_target = "8.0"
spec.watchos.deployment_target = "2.0"
spec.tvos.deployment_target = "9.0"
spec.source_files = "Sources/OpenCombineDispatch/**/*.swift"
spec.dependency "OpenCombine", '>= 0.6'
end
-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
}
+9 -6
View File
@@ -6,13 +6,16 @@ let package = Package(
name: "OpenCombine",
products: [
.library(name: "OpenCombine", targets: ["OpenCombine"]),
],
dependencies: [
.package(url: "https://github.com/broadwaylamb/GottaGoFast.git", from: "0.1.0")
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
],
targets: [
.target(name: "OpenCombine"),
.target(name: "COpenCombineHelpers"),
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine", "GottaGoFast"])
]
dependencies: ["OpenCombine",
"OpenCombineDispatch"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
],
cxxLanguageStandard: .cxx1z
)
+61 -2
View File
@@ -1,8 +1,10 @@
# OpenCombine
[![Build Status](https://travis-ci.org/broadwaylamb/OpenCombine.svg?branch=master)](https://travis-ci.org/broadwaylamb/OpenCombine)
[![CircleCI](https://circleci.com/gh/broadwaylamb/OpenCombine/tree/master.svg?style=svg)](https://circleci.com/gh/broadwaylamb/OpenCombine/tree/master)
[![codecov](https://codecov.io/gh/broadwaylamb/OpenCombine/branch/master/graph/badge.svg)](https://codecov.io/gh/broadwaylamb/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)
![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.
@@ -10,6 +12,42 @@ The main goal of this project is to provide a compatible, reliable and efficient
The project is in early development.
### Installation
`OpenCombine` contains two public targets: `OpenCombine` and `OpenCombineDispatch` (the third one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`.
##### Swift Package Manager
###### Swift Package
To add `OpenCombine` to your [SPM](https://swift.org/package-manager/) package, add the `OpenCombine` package to the list of package and target dependencies in your `Package.swift` file.
```swift
dependencies: [
.package(url: "https://github.com/broadwaylamb/OpenCombine.git", from: "0.7.0")
],
targets: [
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine", "OpenCombineDispatch"])
]
```
###### Xcode
`OpenCombine` can also be added as a SPM dependency directly in your Xcode project *(requires Xcode 11 upwards)*.
To do so, open Xcode, use **File****Swift Packages****Add Package Dependency…**, enter the [repository URL](https://github.com/broadwaylamb/OpenCombine.git), choose the latest available version, and activate the checkboxes:
<p align="center">
<img alt="Select the OpenCombine and OpenCombineDispatch targets"
src="https://user-images.githubusercontent.com/16309982/67618468-bd379f80-f7f8-11e9-917f-e76e878a1aee.png" width="70%">
</p>
##### CocoaPods
To add `OpenCombine` to a project using [CocoaPods](https://cocoapods.org/), add `OpenCombine` and `OpenCombineDispatch` to the list of target dependencies in your `Podfile`.
```ruby
pod 'OpenCombine', '~> 0.7'
pod 'OpenCombineDispatch', '~> 0.7'
```
### Contributing
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
@@ -21,9 +59,30 @@ You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
```
$ swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
$ make test-compatibility
```
Or enable the `-DOPENCOMBINE_COMPATIBILITY_TEST` compiler flag in Xcode's build settings. Note that on iOS only the latter will work.
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/broadwaylamb/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
#### GYB
Some publishers in OpenCombine (like `Publishers.MapKeyPath`, `Publishers.Merge`) exist in several
different flavors in order to support several arities. For example, there are also `Publishers.MapKeyPath2`
and `Publishers.MapKeyPath3`, which are very similar but different enough that Swift's type system
can't help us here (because there's no support for variadic generics). Maintaining multiple instances of
those generic types is tedious and error-prone (they can get out of sync), so we use the GYB tool for
generating those instances from a template.
GYB is a Python script that evaluates Python code written inside a template file, so it's very flexible —
templates can be arbitrarily complex. There is a good article about GYB on
[NSHipster](https://nshipster.com/swift-gyb/).
GYB is part of the [Swift Open Source Project](https://github.com/apple/swift/blob/master/utils/gyb.py)
and can be distributed under the same license as Swift itself.
GYB template files have the `.gyb` extension. Run `make gyb` to generate Swift code from those
templates. The generated files are prefixed with `GENERATED-` and are checked into source control. Those
files should never be edited directly. Instead, the `.gyb` template should be edited, and after that the files
should be regenerated using `make gyb`.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,238 @@
//
// COpenCombineHelpers.cpp
//
//
// Created by Sergej Jaskiewicz on 23/09/2019.
//
#include "COpenCombineHelpers.h"
#include <atomic>
#include <cstdlib>
#include <system_error>
#include <pthread.h>
#ifdef __APPLE__
#include <os/lock.h>
#endif // __APPLE__
// Throwing exceptions through language boundaries is undefined behavior,
// so we must catch all of them in our extern "C" functions.
#define OPENCOMBINE_HANDLE_EXCEPTION_BEGIN try {
// std::terminate will print the type and the error message of the in-flight exception.
#define OPENCOMBINE_HANDLE_EXCEPTION_END } catch (...) { std::terminate(); }
// See 'double expansion trick'
#define OPENCOMBINE_STRINGIFY(value) #value
#define OPENCOMBINE_STRINGIFY_(value) OPENCOMBINE_STRINGIFY(value)
#define OPENCOMBINE_STRING_LINE_NUMBER OPENCOMBINE_STRINGIFY_(__LINE__)
// Throw an exception if the argument is non-zero with filename and line where the error
// occured.
#define OPENCOMBINE_HANDLE_PTHREAD_CALL(errc) \
if ((errc) != 0) { \
const char* what = __FILE__ ":" OPENCOMBINE_STRING_LINE_NUMBER ": " #errc; \
throw std::system_error((errc), std::system_category(), what); \
}
namespace {
std::atomic<uint64_t> next_combine_identifier;
class PlatformIndependentMutex {
public:
virtual void lock() = 0;
virtual void unlock() = 0;
virtual void assertOwner() {}
virtual ~PlatformIndependentMutex() noexcept(false) {}
};
class PThreadMutex : PlatformIndependentMutex {
private:
pthread_mutex_t mutex_;
public:
PThreadMutex() {
Attributes attrs;
attrs.setErrorCheck();
initialize(attrs);
}
PThreadMutex(const PThreadMutex&) = delete;
PThreadMutex& operator=(const PThreadMutex&) = delete;
PThreadMutex(PThreadMutex&&) = delete;
PThreadMutex& operator=(PThreadMutex&&) = delete;
void lock() override final {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_lock(&mutex_));
}
void unlock() override final {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_unlock(&mutex_));
}
~PThreadMutex() {
// Yep, this destructor may throw. This is deliberate, since pthread_mutex_destroy
// may fail.
//
// The altrenative is to just silently ignore the error, which is even worse.
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_destroy(&mutex_));
}
protected:
class Attributes {
pthread_mutexattr_t attrs_;
public:
Attributes() {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_init(&attrs_));
}
Attributes(const Attributes&) = delete;
Attributes& operator=(const Attributes&) = delete;
Attributes(Attributes&&) = delete;
Attributes& operator=(Attributes&&) = delete;
const pthread_mutexattr_t* raw() const noexcept {
return &attrs_;
}
void setRecursive() {
setType(PTHREAD_MUTEX_RECURSIVE);
}
void setErrorCheck() {
setType(PTHREAD_MUTEX_ERRORCHECK);
}
~Attributes() noexcept(false) {
// Yep, this destructor may throw. This is deliberate,
// since pthread_mutexattr_destroy may fail.
//
// The altrenative is to just silently ignore the error, which is even worse.
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_destroy(&attrs_));
}
private:
void setType(int type) {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutexattr_settype(&attrs_, type));
}
};
void initialize(const Attributes& attributes) {
OPENCOMBINE_HANDLE_PTHREAD_CALL(pthread_mutex_init(&mutex_, attributes.raw()));
}
};
class PThreadRecursiveMutex final : PThreadMutex {
public:
PThreadRecursiveMutex() {
Attributes attrs;
attrs.setRecursive();
initialize(attrs);
}
PThreadRecursiveMutex(const PThreadRecursiveMutex&) = delete;
PThreadRecursiveMutex& operator=(const PThreadRecursiveMutex&) = delete;
PThreadRecursiveMutex(PThreadRecursiveMutex&&) = delete;
PThreadRecursiveMutex& operator=(PThreadRecursiveMutex&&) = delete;
};
#ifdef __APPLE__
class OS_UNFAIR_LOCK_AVAILABILITY OSUnfairLock final : PlatformIndependentMutex {
os_unfair_lock mutex_ = OS_UNFAIR_LOCK_INIT;
public:
OSUnfairLock() = default;
OSUnfairLock(const OSUnfairLock&) = delete;
OSUnfairLock& operator=(const OSUnfairLock&) = delete;
OSUnfairLock(OSUnfairLock&&) = delete;
OSUnfairLock& operator=(OSUnfairLock&&) = delete;
void lock() override {
os_unfair_lock_lock(&mutex_);
}
void unlock() override {
os_unfair_lock_unlock(&mutex_);
}
void assertOwner() override {
os_unfair_lock_assert_owner(&mutex_);
}
};
#endif // __APPLE__
} // end anonymous namespace
extern "C" {
uint64_t opencombine_next_combine_identifier(void) {
return next_combine_identifier.fetch_add(1);
}
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
#ifdef __APPLE__
if (__builtin_available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) {
return {new OSUnfairLock};
} else {
return {new PThreadMutex};
}
#else
return {new PThreadMutex};
#endif
OPENCOMBINE_HANDLE_EXCEPTION_END
}
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
// TODO: Use os_unfair_recursive_lock on Darwin as soon as it becomes public API.
return {new PThreadRecursiveMutex};
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_lock_lock(OpenCombineUnfairLock lock) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock mutex) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(mutex.opaque)->unlock();
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(mutex.opaque)->assertOwner();
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock lock) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(lock.opaque)->lock();
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock mutex) {
OPENCOMBINE_HANDLE_EXCEPTION_BEGIN
static_cast<PlatformIndependentMutex*>(mutex.opaque)->unlock();
OPENCOMBINE_HANDLE_EXCEPTION_END
}
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock) {
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
}
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock) {
return delete static_cast<PlatformIndependentMutex*>(lock.opaque);
}
} // extern "C"
@@ -0,0 +1,91 @@
//
// COpenCombineHelpers.h
//
//
// Created by Sergej Jaskiewicz on 23/09/2019.
//
#ifndef COPENCOMBINEHELPERS_H
#define COPENCOMBINEHELPERS_H
#include <stdint.h>
#include <signal.h>
#if __has_attribute(swift_name)
# define OPENCOMBINE_SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
#else
# define OPENCOMBINE_SWIFT_NAME(_name)
#endif
#if __has_attribute(always_inline)
# define OPENCOMBINE_ALWAYS_INLINE __attribute__((always_inline))
#else
# define OPENCOMBINE_ALWAYS_INLINE
#endif
#ifdef __cplusplus
extern "C" {
#endif
#pragma mark - CombineIdentifier
uint64_t opencombine_next_combine_identifier(void)
OPENCOMBINE_SWIFT_NAME(__nextCombineIdentifier());
#pragma mark - OpenCombineUnfairLock
/// A wrapper around an opaque pointer for type safety in Swift.
typedef struct OpenCombineUnfairLock {
void* _Nonnull opaque;
} OPENCOMBINE_SWIFT_NAME(__UnfairLock) OpenCombineUnfairLock;
/// Allocates a lock object. The allocated object must be destroyed by calling
/// the destroy() method.
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.allocate());
void opencombine_unfair_lock_lock(OpenCombineUnfairLock)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.lock(self:));
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.unlock(self:));
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.assertOwner(self:));
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.deallocate(self:));
#pragma mark - OpenCombineUnfairRecursiveLock
/// A wrapper around an opaque pointer for type safety in Swift.
typedef struct OpenCombineUnfairRecursiveLock {
void* _Nonnull opaque;
} OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock) OpenCombineUnfairRecursiveLock;
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.allocate());
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.lock(self:));
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.unlock(self:));
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.deallocate(self:));
#pragma mark - Breakpoint
OPENCOMBINE_ALWAYS_INLINE
inline void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
void opencombine_stop_in_debugger(void) {
raise(SIGTRAP);
}
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* COPENCOMBINEHELPERS_H */
+3 -2
View File
@@ -10,6 +10,7 @@
/// Subscriber implementations can use this type to provide a cancellation token that
/// makes it possible for a caller to cancel a publisher, but not to use the
/// `Subscription` object to request items.
/// An AnyCancellable instance automatically calls `cancel()` when deinitialized.
public final class AnyCancellable: Cancellable, Hashable {
private var _cancel: (() -> Void)?
@@ -21,7 +22,7 @@ public final class AnyCancellable: Cancellable, Hashable {
_cancel = cancel
}
public init<CancellableType: Cancellable>(_ canceller: CancellableType) {
public init<OtherCancellable: Cancellable>(_ canceller: OtherCancellable) {
_cancel = canceller.cancel
}
@@ -39,7 +40,7 @@ public final class AnyCancellable: Cancellable, Hashable {
}
deinit {
cancel()
_cancel?()
}
}
+35 -15
View File
@@ -5,12 +5,25 @@
// Created by Sergej Jaskiewicz on 10.06.2019.
//
extension Publisher {
/// Wraps this publisher with a type eraser.
///
/// Use `eraseToAnyPublisher()` to expose an instance of `AnyPublisher` to
/// the downstream subscriber, rather than this publishers actual type.
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
return .init(self)
}
}
/// A type-erasing publisher.
///
/// Use `AnyPublisher` to wrap a publisher whose type has details you dont want to expose
/// to subscribers or other publishers.
public struct AnyPublisher<Output, Failure: Error> {
public struct AnyPublisher<Output, Failure: Error>
: CustomStringConvertible,
CustomPlaygroundDisplayConvertible
{
@usableFromInline
internal let box: PublisherBoxBase<Output, Failure>
@@ -24,6 +37,14 @@ public struct AnyPublisher<Output, Failure: Error> {
{
box = PublisherBox(base: publisher)
}
public var description: String {
return "AnyPublisher"
}
public var playgroundDescription: Any {
return description
}
}
extension AnyPublisher: Publisher {
@@ -36,10 +57,10 @@ extension AnyPublisher: Publisher {
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
@inlinable
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
box.receive(subscriber: subscriber)
box.subscribe(subscriber)
}
}
@@ -51,19 +72,18 @@ internal class PublisherBoxBase<Output, Failure: Error>: Publisher {
@inlinable
internal init() {}
@inlinable
internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
@usableFromInline
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
fatalError()
abstractMethod()
}
}
@usableFromInline
internal final class PublisherBox<PublisherType: Publisher>
: PublisherBoxBase<PublisherType.Output,
PublisherType.Failure> {
: PublisherBoxBase<PublisherType.Output, PublisherType.Failure>
{
@usableFromInline
internal let base: PublisherType
@@ -74,9 +94,9 @@ internal final class PublisherBox<PublisherType: Publisher>
}
@inlinable
override internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
override internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
base.receive(subscriber: subscriber)
base.subscribe(subscriber)
}
}
-116
View File
@@ -1,116 +0,0 @@
//
// AnySubject.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
public final class AnySubject<Output, Failure: Error>: Subject {
private let _box: SubjectBoxBase<Output, Failure>
public init<SubjectType: Subject>(_ subject: SubjectType)
where Output == SubjectType.Output, Failure == SubjectType.Failure
{
_box = SubjectBox(base: subject)
}
public init(
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
_ send: @escaping (Output) -> Void,
_ sendCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
) {
_box = ClosureBasedSubject(subscribe, send, sendCompletion)
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
_box.receive(subscriber: subscriber)
}
public func send(_ value: Output) {
_box.send(value)
}
public func send(completion: Subscribers.Completion<Failure>) {
_box.send(completion: completion)
}
}
/// A type-erasing base class. Its concrete subclass is generic over the underlying
/// publisher.
private class SubjectBoxBase<Output, Failure: Error>: Subject {
func send(_ value: Output) {
fatalError()
}
func send(completion: Subscribers.Completion<Failure>) {
fatalError()
}
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
fatalError()
}
}
private final class SubjectBox<SubjectType: Subject>
: SubjectBoxBase<SubjectType.Output,
SubjectType.Failure> {
private let base: SubjectType
init(base: SubjectType) {
self.base = base
}
override func send(_ value: Output) {
base.send(value)
}
override func send(completion: Subscribers.Completion<Failure>) {
base.send(completion: completion)
}
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
base.receive(subscriber: subscriber)
}
}
private final class ClosureBasedSubject<Output, Failure: Error>
: SubjectBoxBase<Output, Failure>
{
private let _subscribe: (AnySubscriber<Output, Failure>) -> Void
private let _receive: (Output) -> Void
private let _receiveCompletion: (Subscribers.Completion<Failure>) -> Void
init(
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
_ receive: @escaping (Output) -> Void,
_ receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
) {
_subscribe = subscribe
_receive = receive
_receiveCompletion = receiveCompletion
}
override func send(_ value: Output) {
_receive(value)
}
override func send(completion: Subscribers.Completion<Failure>) {
_receiveCompletion(completion)
}
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
_subscribe(AnySubscriber(subscriber))
}
}
+123 -90
View File
@@ -15,32 +15,65 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
private let _box: SubscriberBoxBase<Input, Failure>
@usableFromInline
internal let box: AnySubscriberBase<Input, Failure>
@usableFromInline
internal let descriptionThunk: () -> String
@usableFromInline
internal let customMirrorThunk: () -> Mirror
@usableFromInline
internal let playgroundDescriptionThunk: () -> Any
public let combineIdentifier: CombineIdentifier
public var description: String { return _box.description }
public var description: String { return descriptionThunk() }
public var customMirror: Mirror { return _box.customMirror }
public var customMirror: Mirror { return customMirrorThunk() }
/// A custom playground description for this instance.
public var playgroundDescription: Any { return description }
public var playgroundDescription: Any { return playgroundDescriptionThunk() }
/// Creates a type-erasing subscriber to wrap an existing subscriber.
///
/// - Parameter s: The subscriber to type-erase.
public init<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
where Input == SubscriberType.Input, Failure == SubscriberType.Failure
@inline(__always)
@inlinable
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
where Input == Subscriber.Input, Failure == Subscriber.Failure
{
_box = SubscriberBox(base: subscriber)
combineIdentifier = subscriber.combineIdentifier
box = AnySubscriberBox(subscriber)
if let description = subscriber as? CustomStringConvertible {
descriptionThunk = { description.description }
} else {
let fixedDescription = String(describing: type(of: subscriber))
descriptionThunk = { fixedDescription }
}
customMirrorThunk = {
(subscriber as? CustomReflectable)?.customMirror
?? Mirror(subscriber, children: EmptyCollection())
}
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
} else if let desccription = subscriber as? CustomStringConvertible {
playgroundDescriptionThunk = { desccription.description }
} else {
let fixedDescription = String(describing: type(of: subscriber))
playgroundDescriptionThunk = { fixedDescription }
}
}
public init<SubjectType: Subject>(_ subject: SubjectType)
where Input == SubjectType.Output, Failure == SubjectType.Failure
public init<Subject: OpenCombine.Subject>(_ subject: Subject)
where Input == Subject.Output, Failure == Subject.Failure
{
_box = SubjectSubscriber(subject)
combineIdentifier = CombineIdentifier(_box)
self.init(SubjectSubscriber(subject))
}
/// Creates a type-erasing subscriber that executes the provided closures.
@@ -52,140 +85,140 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
/// the publisher.
/// - receiveCompletion: A closure to execute when the subscriber receives
/// a completion callback from the publisher.
@inline(__always)
@inlinable
public init(receiveSubscription: ((Subscription) -> Void)? = nil,
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
_box = ClosureBasedSubscriber(receiveSubscription: receiveSubscription,
receiveValue: receiveValue,
receiveCompletion: receiveCompletion)
box = ClosureBasedAnySubscriber(
receiveSubscription ?? { _ in },
receiveValue ?? { _ in .none },
receiveCompletion ?? { _ in }
)
combineIdentifier = CombineIdentifier()
descriptionThunk = { "Anonymous AnySubscriber" }
customMirrorThunk = { Mirror(reflecting: "Anonymous AnySubscriber") }
playgroundDescriptionThunk = { "Anonymous AnySubscriber" }
}
@inline(__always)
@inlinable
public func receive(subscription: Subscription) {
_box.receive(subscription: subscription)
box.receive(subscription: subscription)
}
@inline(__always)
@inlinable
public func receive(_ value: Input) -> Subscribers.Demand {
return _box.receive(value)
return box.receive(value)
}
@inline(__always)
@inlinable
public func receive(completion: Subscribers.Completion<Failure>) {
_box.receive(completion: completion)
box.receive(completion: completion)
}
}
/// A type-erasing base class. Its concrete subclass is generic over the underlying
/// publisher.
internal class SubscriberBoxBase<Input, Failure: Error>: Subscriber,
CustomStringConvertible,
CustomReflectable {
/// subscriber.
@usableFromInline
internal class AnySubscriberBase<Input, Failure: Error>: Subscriber {
@inline(__always)
@inlinable
internal init() {}
@inline(__always)
@inlinable
deinit {}
@usableFromInline
internal func receive(subscription: Subscription) {
fatalError()
abstractMethod()
}
@usableFromInline
internal func receive(_ input: Input) -> Subscribers.Demand {
fatalError()
abstractMethod()
}
@usableFromInline
internal func receive(completion: Subscribers.Completion<Failure>) {
fatalError()
}
internal var description: String { return "AnySubscriber" }
internal var customMirror: Mirror {
return Mirror(combineIdentifier, children: EmptyCollection())
abstractMethod()
}
}
internal final class SubscriberBox<SubscriberType: Subscriber>
: SubscriberBoxBase<SubscriberType.Input, SubscriberType.Failure>
@usableFromInline
internal final class AnySubscriberBox<Base: Subscriber>
: AnySubscriberBase<Base.Input, Base.Failure>
{
@usableFromInline
internal let base: Base
private let base: SubscriberType
internal init(base: SubscriberType) {
@inlinable
internal init(_ base: Base) {
self.base = base
}
@inlinable
deinit {}
@inlinable
override internal func receive(subscription: Subscription) {
base.receive(subscription: subscription)
}
override internal func receive(_ input: Input) -> Subscribers.Demand {
@inlinable
override internal func receive(_ input: Base.Input) -> Subscribers.Demand {
return base.receive(input)
}
override internal func receive(completion: Subscribers.Completion<Failure>) {
@inlinable
override internal func receive(completion: Subscribers.Completion<Base.Failure>) {
base.receive(completion: completion)
}
override internal var customMirror: Mirror { return Mirror(reflecting: base) }
override internal var description: String { return String(describing: base) }
}
internal final class ClosureBasedSubscriber<Input, Failure: Error>
: SubscriberBoxBase<Input, Failure>
@usableFromInline
internal final class ClosureBasedAnySubscriber<Input, Failure: Error>
: AnySubscriberBase<Input, Failure>
{
@usableFromInline
internal let receiveSubscriptionThunk: (Subscription) -> Void
private let _receiveSubscription: ((Subscription) -> Void)?
private let _receiveValue: ((Input) -> Subscribers.Demand)?
private let _receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)?
@usableFromInline
internal let receiveValueThunk: (Input) -> Subscribers.Demand
internal init(receiveSubscription: ((Subscription) -> Void)? = nil,
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
_receiveSubscription = receiveSubscription
_receiveValue = receiveValue
_receiveCompletion = receiveCompletion
@usableFromInline
internal let receiveCompletionThunk: (Subscribers.Completion<Failure>) -> Void
@inlinable
internal init(_ rcvSubscription: @escaping (Subscription) -> Void,
_ rcvValue: @escaping (Input) -> Subscribers.Demand,
_ rcvCompletion: @escaping (Subscribers.Completion<Failure>) -> Void) {
receiveSubscriptionThunk = rcvSubscription
receiveValueThunk = rcvValue
receiveCompletionThunk = rcvCompletion
}
@inlinable
deinit {}
@inlinable
override internal func receive(subscription: Subscription) {
_receiveSubscription?(subscription)
receiveSubscriptionThunk(subscription)
}
@inlinable
override internal func receive(_ input: Input) -> Subscribers.Demand {
return _receiveValue?(input) ?? .none
return receiveValueThunk(input)
}
@inlinable
override internal func receive(completion: Subscribers.Completion<Failure>) {
_receiveCompletion?(completion)
}
}
internal final class SubjectSubscriber<SubjectType: Subject>
: SubscriberBoxBase<SubjectType.Output, SubjectType.Failure>
{
internal var parent: SubjectType?
internal var upstreamSubscription: Subscription?
internal init(_ parent: SubjectType) {
self.parent = parent
}
override internal func receive(subscription: Subscription) {
upstreamSubscription = subscription
subscription.request(.unlimited)
}
override internal func receive(_ input: Input) -> Subscribers.Demand {
parent?.send(input)
return .none
}
override internal func receive(completion: Subscribers.Completion<Failure>) {
parent?.send(completion: completion)
}
override internal var description: String { return "Subject" }
override internal var customMirror: Mirror {
let children: [(label: String?, value: Any)] = [
(label: "parent", value: parent as Any),
(label: "upstreamSubscription", value: upstreamSubscription as Any)
]
return Mirror(self, children: children)
receiveCompletionThunk(completion)
}
}
-16
View File
@@ -1,16 +0,0 @@
//
// Cancelable.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
/// A protocol indicating that an activity or action may be canceled.
///
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
/// as timers, network access, or disk I/O.
public protocol Cancellable {
/// Cancel the activity.
func cancel()
}
+35
View File
@@ -0,0 +1,35 @@
//
// Cancellable.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
/// A protocol indicating that an activity or action may be canceled.
///
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
/// as timers, network access, or disk I/O.
public protocol Cancellable {
/// Cancel the activity.
func cancel()
}
extension Cancellable {
/// Stores this Cancellable in the specified collection.
/// Parameters:
/// - collection: The collection to store this Cancellable.
public func store<Cancellables: RangeReplaceableCollection>(
in collection: inout Cancellables
) where Cancellables.Element == AnyCancellable {
AnyCancellable(self).store(in: &collection)
}
/// Stores this Cancellable in the specified set.
/// Parameters:
/// - collection: The set to store this Cancellable.
public func store(in set: inout Set<AnyCancellable>) {
AnyCancellable(self).store(in: &set)
}
}
+8 -20
View File
@@ -5,35 +5,23 @@
// Created by Sergej Jaskiewicz on 10.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
public struct CombineIdentifier: Hashable, CustomStringConvertible {
@usableFromInline
internal static var _counter: UInt = 0
private let value: UInt64
@usableFromInline
internal static var _counterLock = Lock(recursive: false)
@usableFromInline
internal let _id: UInt
@inlinable
public init() {
var id: UInt = 0
CombineIdentifier._counterLock.do {
id = CombineIdentifier._counter
CombineIdentifier._counter += 1
}
_id = id
value = __nextCombineIdentifier()
}
public init(_ obj: AnyObject) {
_id = UInt(bitPattern: ObjectIdentifier(obj))
value = UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
public var description: String {
return "0x\(String(_id, radix: 16))"
return "0x\(String(value, radix: 16))"
}
}
+53 -13
View File
@@ -5,21 +5,34 @@
// Created by Sergej Jaskiewicz on 11.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
/// A subject that wraps a single value and publishes a new element whenever the value
/// changes.
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
private let _lock = Lock(recursive: true)
private let _lock = UnfairRecursiveLock.allocate()
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
private var _value: Output
private var _completion: Subscribers.Completion<Failure>?
internal var upstreamSubscriptions: [Subscription] = []
internal var hasAnyDownstreamDemand = false
/// The value wrapped by this subject, published as a new element whenever it changes.
public var value: Output {
didSet {
send(value)
get {
return _value
}
set {
send(newValue)
}
}
@@ -27,23 +40,45 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
///
/// - Parameter value: The initial value to publish.
public init(_ value: Output) {
self.value = value
self._value = value
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
_lock.do {
_subscriptions.append(subscription)
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
}
_lock.deallocate()
}
subscriber.receive(subscription: subscription)
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
subscription.request(.unlimited)
}
}
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Output == Subscriber.Input, Failure == Subscriber.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
}
}
public func send(_ input: Output) {
_lock.do {
_value = input
for subscription in _subscriptions where !subscription.isCompleted {
if subscription._demand > 0 {
subscription._offer(input)
@@ -102,7 +137,7 @@ extension CurrentValueSubject {
}
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else { return }
precondition(demand > 0)
_parent?._lock.do {
if !_delivered, let value = _parent?.value {
_offer(value)
@@ -111,6 +146,7 @@ extension CurrentValueSubject {
} else {
_demand = demand
}
_parent?.hasAnyDownstreamDemand = true
}
}
@@ -119,3 +155,7 @@ extension CurrentValueSubject {
}
}
}
extension CurrentValueSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "CurrentValueSubject" }
}
+122
View File
@@ -0,0 +1,122 @@
//
// Future.swift
//
//
// Created by Max Desiatov on 24/11/2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
/// A publisher that eventually produces one value and then finishes or fails.
public final class Future<Output, Failure>: Publisher where Failure: Error {
public typealias Promise = (Result<Output, Failure>) -> Void
private let _lock = UnfairRecursiveLock.allocate()
private var _subscriptions: [Conduit] = []
private var result: Result<Output, Failure>?
public init(
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
) {
attemptToFulfill { result in
self._lock.do {
guard self.result == nil else { return }
self.result = result
self._publish(result)
}
}
}
deinit {
_lock.deallocate()
}
/// This function is called to attach the specified `Subscriber` to this
/// `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(
subscriber: Downstream
) where Output == Downstream.Input, Failure == Downstream.Failure {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
private func _acknowledgeDownstreamDemand() {
_lock.do {
guard let result = result else { return }
_publish(result)
}
}
private func _publish(_ result: Result<Output, Failure>) {
for subscription in self._subscriptions where !subscription._isCompleted {
switch result {
case let .success(output) where subscription._demand > 0:
subscription._demand -= 1
subscription._demand += subscription._downstream?.receive(output) ?? .none
subscription._receive(completion: .finished)
case let .failure(error):
subscription._receive(completion: .failure(error))
// nothing to do if no demand
default: ()
}
}
}
}
extension Future {
fileprivate final class Conduit: Subscription {
fileprivate var _parent: Future<Output, Failure>?
fileprivate var _downstream: AnySubscriber<Output, Failure>?
fileprivate var _demand: Subscribers.Demand = .none
fileprivate var _isCompleted: Bool {
return _parent == nil
}
fileprivate init(parent: Future<Output, Failure>,
downstream: AnySubscriber<Output, Failure>) {
_parent = parent
_downstream = downstream
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !_isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
}
}
fileprivate func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
_parent?._lock.do {
_demand += demand
}
_parent?._acknowledgeDownstreamDemand()
}
fileprivate func cancel() {
_parent = nil
}
}
}
extension Future.Conduit: CustomStringConvertible {
fileprivate var description: String { return "Future" }
}
@@ -0,0 +1,180 @@
//
// FilterProducer.swift
//
//
// Created by Sergej Jaskiewicz on 23.10.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
/// A helper class that acts like both subscriber and subscription.
///
/// Filter-like operators send an instance of their `Inner` class that is subclass
/// of this class to the upstream publisher (as subscriber) and
/// to the downstream subcriber (as subscription).
///
/// Filter-like operators include `Publishers.Filter`,
/// `Publishers.RemoveDuplicates`, `Publishers.PrefixWhile` and more.
///
/// Subclasses must override the `receive(newValue:)` and `description`.
internal class FilterProducer<Downstream: Subscriber,
Input,
Output,
UpstreamFailure: Error,
Filter>
: CustomStringConvertible,
CustomReflectable
where Downstream.Input == Output
{
// NOTE: This class has been audited for thread safety
// MARK: - State
private enum State {
case awaitingSubscription
case connected(Subscription)
case completed
}
internal final let filter: Filter
internal final let downstream: Downstream
private let lock = UnfairLock.allocate()
private var state = State.awaitingSubscription
internal init(downstream: Downstream, filter: Filter) {
self.downstream = downstream
self.filter = filter
}
deinit {
lock.deallocate()
}
// MARK: - Abstract methods
internal func receive(
newValue: Input
) -> PartialCompletion<Output?, Downstream.Failure> {
abstractMethod()
}
internal var description: String {
abstractMethod()
}
// MARK: - CustomReflectable
internal var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
return Mirror(self, children: children)
}
}
extension FilterProducer: Subscriber {
internal func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .connected(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
internal func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
switch state {
case .awaitingSubscription:
lock.unlock()
fatalError("Invalid state: Received value before receiving subscription")
case .completed:
lock.unlock()
case let .connected(subscription):
lock.unlock()
switch receive(newValue: input) {
case let .continue(output?):
return downstream.receive(output)
case .continue(nil):
return .max(1)
case .finished:
lock.lock()
state = .completed
lock.unlock()
subscription.cancel()
downstream.receive(completion: .finished)
case let .failure(error):
lock.lock()
state = .completed
lock.unlock()
subscription.cancel()
downstream.receive(completion: .failure(error))
}
}
return .none
}
internal func receive(completion: Subscribers.Completion<UpstreamFailure>) {
lock.lock()
switch state {
case .awaitingSubscription:
lock.unlock()
fatalError("Invalid state: Received completion before receiving subscription")
case .completed:
lock.unlock()
return
case .connected:
state = .completed
lock.unlock()
switch completion {
case .finished:
downstream.receive(completion: .finished)
case let .failure(failure):
downstream.receive(completion: .failure(failure as! Downstream.Failure))
}
}
}
}
extension FilterProducer: Subscription {
internal func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .awaitingSubscription:
lock.unlock()
fatalError("Invalid state: Received request before sending subscription")
case .completed:
lock.unlock()
return
case let .connected(subscription):
lock.unlock()
subscription.request(demand)
}
}
internal func cancel() {
lock.lock()
guard case let .connected(subscription) = state else {
state = .completed
lock.unlock()
return
}
state = .completed
lock.unlock()
subscription.cancel()
}
}
extension FilterProducer: CustomPlaygroundDisplayConvertible {
internal var playgroundDescription: Any { return description }
}
+23
View File
@@ -0,0 +1,23 @@
//
// Locking.swift
//
//
// Created by Sergej Jaskiewicz on 11.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
internal typealias UnfairLock = __UnfairLock
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
extension UnfairRecursiveLock {
@inlinable
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
lock()
defer { unlock() }
return try body()
}
}
@@ -0,0 +1,26 @@
//
// PartialCompletion.swift
//
//
// Created by Sergej Jaskiewicz on 22.09.2019.
//
/// A value of this type is returned by the overridden `receive(newValue:)` method
/// of the `ReduceProducer` and `FilterProducer` classes.
internal enum PartialCompletion<Value, Failure: Error> {
/// Indicate that we should continue accepting the upstream's output.
case `continue`(Value)
/// Indicate that no values should be received from the upstream anymore.
case finished
/// Indicate that there was a failure and we should send it downstream.
case failure(Failure)
}
extension PartialCompletion where Value == Void {
/// Indicate that we should continue accepting the upstream's output.
internal static var `continue`: PartialCompletion { return .continue(()) }
}
@@ -0,0 +1,254 @@
//
// ReduceProducer.swift
//
//
// Created by Sergej Jaskiewicz on 22.09.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
/// A helper class that acts like both subscriber and subscription.
///
/// Reduce-like operators send an instance of their `Inner` class that is subclass
/// of this class to the upstream publisher (as subscriber) and
/// to the downstream subcriber (as subsription).
///
/// Reduce-like operators include `Publishers.Reduce`, `Publishers.TryReduce`,
/// `Publishers.Count`, `Publishers.FirstWhere`, `Publishers.AllSatisfy` and more.
///
/// Subclasses must override the `receive(newValue:)` and `description`.
internal class ReduceProducer<Downstream: Subscriber,
Input,
Output,
UpstreamFailure: Error,
Reducer>
: CustomStringConvertible,
CustomReflectable
where Downstream.Input == Output
{
// NOTE: This class has been audited for thread safety
// MARK: - State
internal final var result: Output?
private let initial: Output?
internal final let reduce: Reducer
private var status = SubscriptionStatus.awaitingSubscription
private let downstream: Downstream
private let lock = UnfairLock.allocate()
private var downstreamRequested = false
private var cancelled = false
private var completed = false
private var upstreamCompleted = false
private var empty = true
internal init(downstream: Downstream, initial: Output?, reduce: Reducer) {
self.downstream = downstream
self.initial = initial
self.result = initial
self.reduce = reduce
}
deinit {
lock.deallocate()
}
// MARK: - Abstract methods
internal func receive(
newValue: Input
) -> PartialCompletion<Void, Downstream.Failure> {
abstractMethod()
}
internal var description: String {
abstractMethod()
}
// MARK: - CustomReflectable
internal var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("downstream", downstream),
("result", result as Any),
("initial", initial as Any),
("status", status)
]
return Mirror(self, children: children)
}
// MARK: - Private
/// - Precondition: `lock` is held.
private func receiveFinished() {
guard !cancelled, !completed, !upstreamCompleted else {
lock.unlock()
// This should never happen, because `receive(completion:)`
// (from which this function is called) early exists if
// `status` is `.terminal`.
assertionFailure("The subscription should have been terminated by now")
return
}
upstreamCompleted = true
self.completed = downstreamRequested || empty
let completed = self.completed
let result = self.result
lock.unlock()
if completed {
sendResultAndFinish(result)
}
}
/// - Precondition: `lock` is held.
private func receiveFailure(_ failure: UpstreamFailure) {
guard !cancelled, !completed, !upstreamCompleted else {
lock.unlock()
// This should never happen, because `receive(completion:)`
// (from which this function is called) early exists if
// `status` is `.terminal`.
assertionFailure("The subscription should have been terminated by now")
return
}
upstreamCompleted = true
completed = true
lock.unlock()
downstream.receive(completion: .failure(failure as! Downstream.Failure))
}
private func sendResultAndFinish(_ result: Output?) {
assert(completed && upstreamCompleted)
if let result = result {
_ = downstream.receive(result)
}
downstream.receive(completion: .finished)
}
// MARK: -
}
extension ReduceProducer: Subscriber {
internal func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
subscription.request(.unlimited)
}
internal func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return .none
}
empty = false
lock.unlock()
// Combine doesn't hold the lock when calling `receive(newValue:)`.
//
// This can lead to data races if the contract is violated
// (like when we receive input from multiple threads simultaneously).
switch self.receive(newValue: input) {
case .continue:
break
case .finished:
lock.lock()
upstreamCompleted = true
let downstreamRequested = self.downstreamRequested
if downstreamRequested {
completed = true
}
status = .terminal
let result = self.result
lock.unlock()
subscription.cancel()
guard downstreamRequested else { break }
sendResultAndFinish(result)
case let .failure(error):
lock.lock()
upstreamCompleted = true
completed = true
status = .terminal
lock.unlock()
subscription.cancel()
downstream.receive(completion: .failure(error))
}
return .none
}
internal func receive(completion: Subscribers.Completion<UpstreamFailure>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
switch completion {
case .finished:
receiveFinished()
case let .failure(error):
receiveFailure(error)
}
}
}
extension ReduceProducer: Subscription {
internal func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard !downstreamRequested, !cancelled, !completed else {
lock.unlock()
return
}
downstreamRequested = true
guard upstreamCompleted else {
lock.unlock()
return
}
completed = true
let result = self.result
lock.unlock()
sendResultAndFinish(result)
}
internal func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
cancelled = true
status = .terminal
lock.unlock()
subscription.cancel()
}
}
extension ReduceProducer: CustomPlaygroundDisplayConvertible {
internal var playgroundDescription: Any { return description }
}
@@ -0,0 +1,107 @@
//
// SubjectSubscriber.swift
//
//
// Created by Sergej Jaskiewicz on 16/09/2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
// NOTE: This class has been audited for thread safety.
internal final class SubjectSubscriber<Downstream: Subject>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible,
Subscription
{
private let lock = UnfairLock.allocate()
private var downstreamSubject: Downstream?
private var upstreamSubscription: Subscription?
private var isCancelled: Bool { return downstreamSubject == nil }
internal init(_ parent: Downstream) {
self.downstreamSubject = parent
}
deinit {
lock.deallocate()
}
internal func receive(subscription: Subscription) {
lock.lock()
guard upstreamSubscription == nil, let subject = downstreamSubject else {
lock.unlock()
return
}
upstreamSubscription = subscription
lock.unlock()
subject.send(subscription: self)
}
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
lock.lock()
guard let downstreamSubject = downstreamSubject else {
lock.unlock()
return .none
}
guard upstreamSubscription != nil else { APIViolationValueBeforeSubscription() }
lock.unlock()
downstreamSubject.send(input)
return .none
}
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
lock.lock()
guard let subject = downstreamSubject else {
lock.unlock()
return
}
guard upstreamSubscription != nil else { APIViolationUnexpectedCompletion() }
lock.unlock()
subject.send(completion: completion)
downstreamSubject = nil
}
internal var description: String { return "Subject" }
internal var playgroundDescription: Any { return description }
internal var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstreamSubject", downstreamSubject as Any),
("upstreamSubscription", upstreamSubscription as Any),
("subject", downstreamSubject as Any)
]
return Mirror(self, children: children)
}
internal func request(_ demand: Subscribers.Demand) {
lock.lock()
guard let subscription = upstreamSubscription else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
internal func cancel() {
lock.lock()
if isCancelled {
lock.unlock()
return
}
guard let subscription = upstreamSubscription else {
lock.unlock()
return
}
upstreamSubscription = nil
downstreamSubject = nil
lock.unlock()
subscription.cancel()
}
}
@@ -0,0 +1,12 @@
//
// SubscriptionStatus.swift
//
//
// Created by Sergej Jaskiewicz on 21.09.2019.
//
internal enum SubscriptionStatus {
case awaitingSubscription
case subscribed(Subscription)
case terminal
}
@@ -0,0 +1,33 @@
//
// Violations.swift
//
//
// Created by Sergej Jaskiewicz on 16/09/2019.
//
internal func APIViolationValueBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("""
API Violation: received an unexpected value before receiving a Subscription
""",
file: file,
line: line)
}
internal func APIViolationUnexpectedCompletion(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("API Violation: received an unexpected completion", file: file, line: line)
}
internal func abstractMethod(file: StaticString = #file, line: UInt = #line) -> Never {
fatalError("Abstract method call", file: file, line: line)
}
extension Subscribers.Demand {
internal func assertNonZero(file: StaticString = #file,
line: UInt = #line) {
if self == .none {
fatalError("API Violation: demand must not be zero", file: file, line: line)
}
}
}
+6 -8
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.
@@ -139,9 +140,7 @@ public struct ImmediateScheduler: Scheduler {
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void) {
fatalError(
"Attempt to schedule something in the future on the immediate scheduler"
)
action()
}
/// Performs the action at some time after the specified date, at the specified
@@ -151,8 +150,7 @@ public struct ImmediateScheduler: Scheduler {
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void) -> Cancellable {
fatalError(
"Attempt to schedule something in the future on the immediate scheduler"
)
action()
return Subscriptions.empty
}
}
-53
View File
@@ -1,53 +0,0 @@
//
// Locking.swift
//
//
// Created by Sergej Jaskiewicz on 11.06.2019.
//
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#else
#error("How to do locking on this platform?")
#endif
@usableFromInline
internal final class Lock {
@usableFromInline
internal var _mutex = pthread_mutex_t()
@inlinable
internal init(recursive: Bool) {
var attrib = pthread_mutexattr_t()
pthread_mutexattr_init(&attrib)
if recursive {
pthread_mutexattr_settype(&attrib, Int32(PTHREAD_MUTEX_RECURSIVE))
}
pthread_mutex_init(&_mutex, &attrib)
}
@inlinable
deinit {
pthread_mutex_destroy(&_mutex)
}
@inlinable
internal func _lock() {
pthread_mutex_lock(&_mutex)
}
@inlinable
internal func _unlock() {
pthread_mutex_unlock(&_mutex)
}
@inlinable
internal func `do`<Result>(_ body: () throws -> Result) rethrows -> Result {
_lock()
defer { _unlock() }
return try body()
}
}
@@ -0,0 +1,85 @@
//
// ObservableObject.swift
//
//
// Created by Sergej Jaskiewicz on 08/09/2019.
//
/// A type of object with a publisher that emits before the object has changed.
///
/// By default an `ObservableObject` will synthesize an `objectWillChange`
/// publisher that emits before any of its `@Published` properties changes:
///
/// class Contact : ObservableObject {
/// @Published var name: String
/// @Published var age: Int
///
/// init(name: String, age: Int) {
/// self.name = name
/// self.age = age
/// }
///
/// func haveBirthday() -> Int {
/// age += 1
/// }
/// }
///
/// let john = Contact(name: "John Appleseed", age: 24)
/// john.objectWillChange.sink { _ in print("will change") }
/// print(john.haveBirthday)
/// // Prints "will change"
/// // Prints "25"
///
public protocol ObservableObject: AnyObject {
/// The type of publisher that emits before the object has changed.
associatedtype ObjectWillChangePublisher: Publisher = ObservableObjectPublisher
where ObjectWillChangePublisher.Failure == Never
/// A publisher that emits before the object has changed.
var objectWillChange: ObjectWillChangePublisher { get }
}
extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
// swiftlint:disable let_var_whitespace
#if swift(>=5.1)
/// A publisher that emits before the object has changed.
@available(*, unavailable, message: """
The default implementation of objectWillChange is not available yet. \
It's being worked on in \
https://github.com/broadwaylamb/OpenCombine/pull/97
""")
public var objectWillChange: ObservableObjectPublisher {
fatalError("unimplemented")
}
#else
public var objectWillChange: ObservableObjectPublisher {
return ObservableObjectPublisher()
}
#endif
// swiftlint:enable let_var_whitespace
}
/// The default publisher of an `ObservableObject`.
public final class ObservableObjectPublisher: Publisher {
public typealias Output = Void
public typealias Failure = Never
private let subject: PassthroughSubject<Void, Never>
public init() {
subject = .init()
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Void, Downstream.Failure == Never
{
subject.subscribe(subscriber)
}
public func send() {
subject.send()
}
}
+62 -16
View File
@@ -5,37 +5,67 @@
// Created by Sergej Jaskiewicz on 11.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
/// A subject that passes along values and completion.
///
/// Use a `PassthroughSubject` in unit tests when you want a publisher than can publish
/// specific values on-demand during tests.
public final class PassthroughSubject<Output, Failure: Error>: Subject {
private let _lock = Lock(recursive: true)
private let _lock = UnfairRecursiveLock.allocate()
private var _completion: Subscribers.Completion<Failure>?
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
internal var upstreamSubscriptions: [Subscription] = []
internal var hasAnyDownstreamDemand = false
public init() {}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
_lock.do {
_subscriptions.append(subscription)
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
}
_lock.deallocate()
}
subscriber.receive(subscription: subscription)
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
if hasAnyDownstreamDemand {
subscription.request(.unlimited)
}
}
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
}
}
public func send(_ input: Output) {
_lock.do {
for subscription in _subscriptions
where !subscription.isCompleted && subscription._demand > 0
where !subscription._isCompleted && subscription._demand > 0
{
let newDemand = subscription._downstream?.receive(input) ?? .none
subscription._demand += newDemand
@@ -45,13 +75,23 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
}
public func send(completion: Subscribers.Completion<Failure>) {
_completion = completion
_lock.do {
_completion = completion
for subscriber in _subscriptions {
subscriber._receive(completion: completion)
}
}
}
private func _acknowledgeDownstreamDemand() {
_lock.do {
guard !hasAnyDownstreamDemand else { return }
hasAnyDownstreamDemand = true
for subscription in upstreamSubscriptions {
subscription.request(.unlimited)
}
}
}
}
extension PassthroughSubject {
@@ -64,7 +104,7 @@ extension PassthroughSubject {
fileprivate var _demand: Subscribers.Demand = .none
var isCompleted: Bool {
fileprivate var _isCompleted: Bool {
return _parent == nil
}
@@ -75,20 +115,26 @@ extension PassthroughSubject {
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
if !_isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
}
}
func request(_ demand: Subscribers.Demand) {
fileprivate func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
_parent?._lock.do {
_demand = demand
_demand += demand
}
_parent?._acknowledgeDownstreamDemand()
}
func cancel() {
fileprivate func cancel() {
_parent = nil
}
}
}
extension PassthroughSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "PassthroughSubject" }
}
+103
View File
@@ -0,0 +1,103 @@
//
// Published.swift
// OpenCombine
//
// Created by Евгений Богомолов on 01/09/2019.
//
#if swift(>=5.1)
/// Adds a `Publisher` to a property.
///
/// Properties annotated with `@Published` contain both the stored value
/// and a publisher which sends any new values after the property value
/// has been sent. New subscribers will receive the current value
/// of the property first.
/// Note that the `@Published` property is class-constrained.
/// Use it with properties of classes, not with non-class types like structures.
@available(swift, introduced: 5.1)
@propertyWrapper
public struct Published<Value> {
/// Initialize the storage of the `Published` property as well as the corresponding
/// `Publisher`.
public init(initialValue: Value) {
self.init(wrappedValue: initialValue)
}
/// Initialize the storage of the `Published` property as well as the corresponding
/// `Publisher`.
public init(wrappedValue: Value) {
value = wrappedValue
}
/// A publisher for properties marked with the `@Published` attribute.
public struct Publisher: OpenCombine.Publisher {
public typealias Output = Value
public typealias Failure = Never
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Value, Downstream.Failure == Never
{
subject.subscribe(subscriber)
}
fileprivate let subject: OpenCombine.CurrentValueSubject<Value, Never>
fileprivate init(_ output: Output) {
subject = .init(output)
}
}
private var value: Value
private var publisher: Publisher?
internal var objectWillChange: ObservableObjectPublisher?
/// The property that can be accessed with the `$` syntax and allows access to
/// the `Publisher`
public var projectedValue: Publisher {
mutating get {
if let publisher = publisher {
return publisher
}
let publisher = Publisher(value)
self.publisher = publisher
return publisher
}
}
// swiftlint:disable let_var_whitespace
@available(*, unavailable, message: """
@Published is only available on properties of classes
""")
public var wrappedValue: Value {
get { fatalError() }
set { fatalError() } // swiftlint:disable:this unused_setter_value
}
// swiftlint:enable let_var_whitespace
public static subscript<EnclosingSelf: AnyObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
) -> Value {
get {
return object[keyPath: storageKeyPath].value
}
set {
object[keyPath: storageKeyPath].objectWillChange?.send()
object[keyPath: storageKeyPath].publisher?.subject.send(newValue)
object[keyPath: storageKeyPath].value = newValue
}
// TODO: Benchmark and explore a possibility to use _modify
}
}
#else
@available(swift, introduced: 5.1)
public typealias Published = Never
#endif // swift(>=5.1)
+14 -4
View File
@@ -36,8 +36,8 @@ public protocol Publisher {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Failure == Subscriber.Failure, Output == Subscriber.Input
}
extension Publisher {
@@ -52,9 +52,19 @@ extension Publisher {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`. After attaching,
/// the subscriber can start to receive values.
public func subscribe<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
where Failure == Subscriber.Failure, Output == Subscriber.Input
{
receive(subscriber: subscriber)
}
public func subscribe<Subject: OpenCombine.Subject>(
_ subject: Subject
) -> AnyCancellable
where Failure == Subject.Failure, Output == Subject.Output
{
let subscriber = SubjectSubscriber(subject)
self.subscribe(subscriber)
return AnyCancellable(subscriber)
}
}
@@ -0,0 +1,48 @@
//
// Deferred.swift
//
//
// Created by Joseph Spadafora on 7/7/19.
//
/// A publisher that awaits subscription before running the supplied closure
/// to create a publisher for the new subscriber.
public struct Deferred<DeferredPublisher: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = DeferredPublisher.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = DeferredPublisher.Failure
/// The closure to execute when it receives a subscription.
///
/// The publisher returned by this closure immediately
/// receives the incoming subscription.
public let createPublisher: () -> DeferredPublisher
/// Creates a deferred publisher.
///
/// - Parameter createPublisher: The closure to execute
/// when calling `subscribe(_:)`.
public init(createPublisher: @escaping () -> DeferredPublisher) {
self.createPublisher = createPublisher
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Output == Downstream.Input
{
let deferredPublisher = createPublisher()
deferredPublisher.subscribe(subscriber)
}
}
@@ -0,0 +1,53 @@
//
// Empty.swift
//
//
// Created by Sergej Jaskiewicz on 16.06.2019.
//
/// A publisher that never publishes any values, and optionally finishes immediately.
///
/// You can create a Never publisher one which never sends values and never
/// finishes or fails with the initializer `Empty(completeImmediately: false)`.
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
/// Creates an empty publisher.
///
/// - Parameter completeImmediately: A Boolean value that indicates whether
/// the publisher should immediately finish.
public init(completeImmediately: Bool = true) {
self.completeImmediately = completeImmediately
}
/// Creates an empty publisher with the given completion behavior and output and
/// failure types.
///
/// Use this initializer to connect the empty publisher to subscribers or other
/// publishers that have specific output and failure types.
/// - Parameters:
/// - completeImmediately: A Boolean value that indicates whether the publisher
/// should immediately finish.
/// - outputType: The output type exposed by this publisher.
/// - failureType: The failure type exposed by this publisher.
public init(completeImmediately: Bool = true,
outputType: Output.Type,
failureType: Failure.Type) {
self.init(completeImmediately: completeImmediately)
}
/// A Boolean value that indicates whether the publisher immediately sends
/// a completion.
///
/// If `true`, the publisher finishes immediately after sending a subscription
/// to the subscriber. If `false`, it never completes.
public let completeImmediately: Bool
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
if completeImmediately {
subscriber.receive(completion: .finished)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
//
// Fail.swift
//
//
// Created by Sergej Jaskiewicz on 19.06.2019.
//
/// A publisher that immediately terminates with the specified error.
public struct Fail<Output, Failure: Error>: Publisher {
/// Creates a publisher that immediately terminates with the specified failure.
///
/// - Parameter error: The failure to send when terminating the publisher.
public init(error: Failure) {
self.error = error
}
/// Creates publisher with the given output type, that immediately terminates with
/// the specified failure.
///
/// Use this initializer to create a `Fail` publisher that can work with
/// subscribers or publishers that expect a given output type.
/// - Parameters:
/// - outputType: The output type exposed by this publisher.
/// - failure: The failure to send when terminating the publisher.
public init(outputType: Output.Type, failure: Failure) {
self.error = failure
}
/// The failure to send when terminating the publisher.
public let error: Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(error))
}
}
extension Fail: Equatable where Failure: Equatable {}
@@ -0,0 +1,254 @@
//
//
// Auto-generated from GYB template. DO NOT EDIT!
//
//
//
//
// Publishers.Encode.swift.gyb
//
//
// Created by Joseph Spadafora on 6/22/19.
//
extension Publisher {
/// Encodes the output from upstream using a specified `TopLevelEncoder`.
/// For example, use `JSONEncoder`.
public func encode<Coder: TopLevelEncoder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder> {
return .init(upstream: self, encoder: encoder)
}
/// Decodes the output from upstream using a specified `TopLevelDecoder`.
/// For example, use `JSONDecoder`.
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
type: Item.Type,
decoder: Coder
) -> Publishers.Decode<Self, Item, Coder> where Output == Coder.Input {
return .init(upstream: self, decoder: decoder)
}
}
extension Publishers {
public struct Encode<Upstream: Publisher, Coder: TopLevelEncoder>: Publisher
where Upstream.Output: Encodable
{
public typealias Failure = Error
public typealias Output = Coder.Output
public let upstream: Upstream
private let _encode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self._encode = encoder.encode
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, encode: _encode))
}
}
public struct Decode<Upstream: Publisher, Output: Decodable, Coder: TopLevelDecoder>
: Publisher
where Upstream.Output == Coder.Input
{
public typealias Failure = Error
public let upstream: Upstream
private let _decode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decode = { try decoder.decode(Output.self, from: $0) }
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, decode: _decode))
}
}
}
extension Publishers.Encode {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
// NOTE: This class has been audited for thread safety.
// Combine doesn't use any locking here.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let encode: (Upstream.Output) throws -> Output
private var finished = false
private var subscription: Subscription?
fileprivate init(
downstream: Downstream,
encode: @escaping (Upstream.Output) throws -> Output
) {
self.downstream = downstream
self.encode = encode
}
func receive(subscription: Subscription) {
if finished || self.subscription != nil {
subscription.cancel()
return
}
self.subscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
if finished { return .none }
do {
return try downstream.receive(encode(input))
} catch {
finished = true
subscription?.cancel()
subscription = nil
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
if finished { return }
finished = true
subscription = nil
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
subscription?.request(demand)
}
func cancel() {
guard let subscription = self.subscription, !finished else { return }
subscription.cancel()
self.subscription = nil
finished = true
}
var description: String { return "Encode" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("finished", finished),
("upstreamSubscription", subscription as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.Decode {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
// NOTE: This class has been audited for thread safety.
// Combine doesn't use any locking here.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let decode: (Upstream.Output) throws -> Output
private var finished = false
private var subscription: Subscription?
fileprivate init(
downstream: Downstream,
decode: @escaping (Upstream.Output) throws -> Output
) {
self.downstream = downstream
self.decode = decode
}
func receive(subscription: Subscription) {
if finished || self.subscription != nil {
subscription.cancel()
return
}
self.subscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
if finished { return .none }
do {
return try downstream.receive(decode(input))
} catch {
finished = true
subscription?.cancel()
subscription = nil
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
if finished { return }
finished = true
subscription = nil
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
subscription?.request(demand)
}
func cancel() {
guard let subscription = self.subscription, !finished else { return }
subscription.cancel()
self.subscription = nil
finished = true
}
var description: String { return "Decode" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("finished", finished),
("upstreamSubscription", subscription as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,316 @@
//
//
// Auto-generated from GYB template. DO NOT EDIT!
//
//
//
//
// Publishers.MapKeyPath.swift.gyb
//
//
// Created by Sergej Jaskiewicz on 03/10/2019.
//
extension Publisher {
/// Returns a publisher that publishes the values of a keyt path as a tuple.
///
/// - Parameters:
/// - keyPath: The key path of a property on `Output`
/// - Returns: A publisher that publishes the value of the key path.
public func map<Result>(
_ keyPath: KeyPath<Output, Result>
) -> Publishers.MapKeyPath<Self, Result> {
return .init(
upstream: self,
keyPath: keyPath
)
}
/// Returns a publisher that publishes the values of two key paths as a tuple.
///
/// - Parameters:
/// - keyPath0: The key path of a property on `Output`
/// - keyPath1: The key path of another property on `Output`
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
public func map<Result0, Result1>(
_ keyPath0: KeyPath<Output, Result0>,
_ keyPath1: KeyPath<Output, Result1>
) -> Publishers.MapKeyPath2<Self, Result0, Result1> {
return .init(
upstream: self,
keyPath0: keyPath0,
keyPath1: keyPath1
)
}
/// Returns a publisher that publishes the values of three key paths as a tuple.
///
/// - Parameters:
/// - keyPath0: The key path of a property on `Output`
/// - keyPath1: The key path of another property on `Output`
/// - keyPath2: The key path of a third property on `Output`
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
public func map<Result0, Result1, Result2>(
_ keyPath0: KeyPath<Output, Result0>,
_ keyPath1: KeyPath<Output, Result1>,
_ keyPath2: KeyPath<Output, Result2>
) -> Publishers.MapKeyPath3<Self, Result0, Result1, Result2> {
return .init(
upstream: self,
keyPath0: keyPath0,
keyPath1: keyPath1,
keyPath2: keyPath2
)
}
}
extension Publishers {
/// A publisher that publishes the value of a key path.
public struct MapKeyPath<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The key path of a property to publish.
public let keyPath: KeyPath<Upstream.Output, Output>
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber, parent: self))
}
}
/// A publisher that publishes the values of two key paths as a tuple.
public struct MapKeyPath2<Upstream: Publisher, Output0, Output1>: Publisher {
public typealias Output = (Output0, Output1)
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The key path of a property to publish.
public let keyPath0: KeyPath<Upstream.Output, Output0>
/// The key path of a second property to publish.
public let keyPath1: KeyPath<Upstream.Output, Output1>
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber, parent: self))
}
}
/// A publisher that publishes the values of three key paths as a tuple.
public struct MapKeyPath3<Upstream: Publisher, Output0, Output1, Output2>: Publisher {
public typealias Output = (Output0, Output1, Output2)
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The key path of a property to publish.
public let keyPath0: KeyPath<Upstream.Output, Output0>
/// The key path of a second property to publish.
public let keyPath1: KeyPath<Upstream.Output, Output1>
/// The key path of a third property to publish.
public let keyPath2: KeyPath<Upstream.Output, Output2>
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber, parent: self))
}
}
}
extension Publishers.MapKeyPath {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let keyPath: KeyPath<Input, Output>
let combineIdentifier = CombineIdentifier()
fileprivate init(
downstream: Downstream,
parent: Publishers.MapKeyPath<Upstream, Output>
) {
self.downstream = downstream
self.keyPath = parent.keyPath
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
let output = (
input[keyPath: keyPath]
)
return downstream.receive(output)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "ValueForKey" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("keyPath", keyPath),
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.MapKeyPath2 {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let keyPath0: KeyPath<Input, Output0>
private let keyPath1: KeyPath<Input, Output1>
let combineIdentifier = CombineIdentifier()
fileprivate init(
downstream: Downstream,
parent: Publishers.MapKeyPath2<Upstream, Output0, Output1>
) {
self.downstream = downstream
self.keyPath0 = parent.keyPath0
self.keyPath1 = parent.keyPath1
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
let output = (
input[keyPath: keyPath0],
input[keyPath: keyPath1]
)
return downstream.receive(output)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "ValueForKeys" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("keyPath0", keyPath0),
("keyPath1", keyPath1),
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.MapKeyPath3 {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let keyPath0: KeyPath<Input, Output0>
private let keyPath1: KeyPath<Input, Output1>
private let keyPath2: KeyPath<Input, Output2>
let combineIdentifier = CombineIdentifier()
fileprivate init(
downstream: Downstream,
parent: Publishers.MapKeyPath3<Upstream, Output0, Output1, Output2>
) {
self.downstream = downstream
self.keyPath0 = parent.keyPath0
self.keyPath1 = parent.keyPath1
self.keyPath2 = parent.keyPath2
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
let output = (
input[keyPath: keyPath0],
input[keyPath: keyPath1],
input[keyPath: keyPath2]
)
return downstream.receive(output)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "ValueForKeys" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("keyPath0", keyPath0),
("keyPath1", keyPath1),
("keyPath2", keyPath2),
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
+99 -126
View File
@@ -11,8 +11,6 @@
/// is also useful when replacing a value with `Catch`.
///
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
/// In contrast with `Publishers.Optional`, a `Just` publisher always produces
/// a value.
public struct Just<Output>: Publisher {
public typealias Failure = Never
@@ -27,8 +25,8 @@ public struct Just<Output>: Publisher {
self.output = output
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where SubscriberType.Input == Output, SubscriberType.Failure == Never
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Never
{
subscriber.receive(subscription: Inner(value: output, downstream: subscriber))
}
@@ -66,8 +64,8 @@ extension Just {
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(Result { try predicate(output) })
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try predicate(output) })
}
public func contains(where predicate: (Output) -> Bool) -> Just<Bool> {
@@ -76,8 +74,8 @@ extension Just {
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(Result { try predicate(output) })
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try predicate(output) })
}
public func collect() -> Just<[Output]> {
@@ -90,45 +88,25 @@ extension Just {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
}
public func max(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Just<Output> {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
}
public func count() -> Just<Int> {
return .init(1)
}
public func dropFirst(
_ count: Int = 1
) -> Publishers.Optional<Output, Never> {
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
precondition(count >= 0, "count must not be negative")
return Publishers.Optional(count > 0 ? nil : output)
return .init(count > 0 ? nil : self.output)
}
public func drop(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? nil : output)
}
public func tryDrop(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? nil : output })
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? nil : output)
}
public func first() -> Just<Output> {
@@ -137,14 +115,8 @@ extension Just {
public func first(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? output : nil)
}
public func tryFirst(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? output : nil })
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? output : nil)
}
public func last() -> Just<Output> {
@@ -153,18 +125,12 @@ extension Just {
public func last(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? output : nil)
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? output : nil)
}
public func tryLast(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? output : nil })
}
public func ignoreOutput() -> Publishers.Empty<Output, Never> {
return Publishers.Empty()
public func ignoreOutput() -> Empty<Output, Never> {
return .init()
}
public func map<ElementOfResult>(
@@ -175,79 +141,61 @@ extension Just {
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(Result { try transform(output) })
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(Result { try transform(output) })
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Never> {
return Publishers.Optional(transform(output))
}
public func tryCompactMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(Result { try transform(output) })
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(transform(output))
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(isIncluded(output) ? output : nil)
) -> Optional<Output>.OCombine.Publisher {
return .init(isIncluded(output) ? output : nil)
}
public func tryFilter(
_ isIncluded: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try isIncluded(output) ? output : nil })
}
public func output(at index: Int) -> Publishers.Optional<Output, Never> {
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
precondition(index >= 0, "index must not be negative")
return Publishers.Optional(index == 0 ? output : nil)
return .init(index == 0 ? output : nil)
}
public func output<RangeExpr: RangeExpression>(
in range: RangeExpr
) -> Publishers.Optional<Output, Never> where RangeExpr.Bound == Int {
public func output<RangeExpression: Swift.RangeExpression>(
in range: RangeExpression
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
let range = range.relative(to: 0..<Int.max)
return Publishers.Optional(range.lowerBound == 0 ? output : nil)
return .init(range.lowerBound == 0 ? output : nil)
// The above implementation is used for compatibility.
//
// It actually probably should be just this:
// return Publishers.Optional(range.contains(0) ? output : nil)
// return .init(range.contains(0) ? output : nil)
}
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Never> {
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
precondition(maxLength >= 0, "maxLength must not be negative")
return Publishers.Optional(maxLength > 0 ? output : nil)
return .init(maxLength > 0 ? output : nil)
}
public func prefix(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? output : nil)
}
public func tryPrefix(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? output : nil })
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? output : nil)
}
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Publishers.Once<Output, Failure> {
return Publishers.Once(output)
) -> Result<Output, Failure>.OCombine.Publisher {
return .init(output)
}
public func mapError<Failure: Error>(
_ transform: (Never) -> Failure
) -> Publishers.Once<Output, Failure> {
return Publishers.Once(output)
) -> Result<Output, Failure>.OCombine.Publisher {
return .init(output)
}
public func removeDuplicates(
@@ -258,9 +206,8 @@ extension Just {
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Error> {
return Publishers
.Once(Result { try _ = predicate(output, output); return output })
) -> Result<Output, Error>.OCombine.Publisher {
return .init(Result { try _ = predicate(output, output); return output })
}
public func replaceError(with output: Output) -> Just<Output> {
@@ -275,66 +222,92 @@ extension Just {
return self
}
public func retry() -> Just<Output> {
return self
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Publishers.Once<Accumulator, Never> {
return Publishers.Once(nextPartialResult(initialResult, output))
) -> Result<Accumulator, Never>.OCombine.Publisher {
return .init(nextPartialResult(initialResult, output))
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Publishers.Once<Accumulator, Error> {
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
) -> Result<Accumulator, Error>.OCombine.Publisher {
return .init(Result { try nextPartialResult(initialResult, output) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Never> {
return Publishers.Once(nextPartialResult(initialResult, output))
) -> Result<ElementOfResult, Never>.OCombine.Publisher {
return .init(nextPartialResult(initialResult, output))
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(Result { try nextPartialResult(initialResult, output) })
}
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)
}
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
extension Just {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
private var downstream: Downstream?
private let value: Output
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
fileprivate init(value: Output, downstream: Downstream) {
self.downstream = downstream
self.value = value
}
}
func cancel() {
_downstream = nil
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
guard let downstream = self.downstream else { return }
self.downstream = nil
_ = downstream.receive(value)
downstream.receive(completion: .finished)
}
var description: String { return "Just" }
func cancel() {
downstream = nil
}
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
var description: String { return "Just" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(value))
}
var playgroundDescription: Any { return description }
}
}
@@ -1,24 +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
}
}
@@ -0,0 +1,289 @@
//
// Optional.Publisher.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Optional {
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
///
/// Combine extends `Optional` with a nested type `Publisher`.
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
/// e. g. when importing Foundation), you will not be able to write
/// `Optional<Int>.Publisher`, because Swift is unable to understand
/// which `Publisher` you're referring to.
///
/// So you have to write `Optional<Int>.OCombine.Publisher`.
///
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
///
/// You can omit this whenever Combine is not available (e. g. on Linux).
public struct OCombine {
fileprivate let optional: Optional
fileprivate init(_ optional: Optional) {
self.optional = optional
}
/// A publisher that publishes an optional value to each subscriber
/// exactly once, if the optional has a value.
///
/// In contrast with `Just`, an `Optional` publisher may send
/// no value before completion.
public struct Publisher: OpenCombine.Publisher {
public typealias Output = Wrapped
public typealias Failure = Never
/// The result to deliver to each subscriber.
public let output: Wrapped?
/// Creates a publisher to emit the optional value of a successful result,
/// or fail with an error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ output: Output?) {
self.output = output
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
if let output = output {
subscriber.receive(subscription: Inner(value: output,
downstream: subscriber))
} else {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .finished)
}
}
}
}
#if !canImport(Combine)
/// A publisher that publishes an optional value to each subscriber
/// exactly once, if the optional has a value.
///
/// In contrast with `Just`, an `Optional` publisher may send
/// no value before completion.
public typealias Publisher = OCombine.Publisher
#endif
}
extension Optional.OCombine {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Wrapped
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
private var downstream: Downstream?
private let output: Wrapped
init(value: Wrapped, downstream: Downstream) {
self.output = value
self.downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
guard let downstream = self.downstream else { return }
self.downstream = nil
_ = downstream.receive(output)
downstream.receive(completion: .finished)
}
func cancel() {
downstream = nil
}
var description: String { return "Optional" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
}
var playgroundDescription: Any { return description }
}
}
extension Optional.OCombine.Publisher: Equatable where Wrapped: Equatable {}
extension Optional.OCombine.Publisher where Wrapped: Equatable {
public func contains(_ output: Output) -> Optional<Bool>.OCombine.Publisher {
return .init(self.output.map { $0 == output })
}
public func removeDuplicates() -> Optional<Wrapped>.OCombine.Publisher {
return self
}
}
extension Optional.OCombine.Publisher where Wrapped: Comparable {
public func min() -> Optional<Wrapped>.OCombine.Publisher {
return self
}
public func max() -> Optional<Wrapped>.OCombine.Publisher {
return self
}
}
extension Optional.OCombine.Publisher {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Optional<Bool>.OCombine.Publisher {
return .init(self.output.map(predicate))
}
public func collect() -> Optional<[Output]>.OCombine.Publisher {
return .init(self.output.map { [$0] } ?? [])
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(self.output.flatMap(transform))
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return self
}
public func max(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return self
}
public func contains(
where predicate: (Output) -> Bool
) -> Optional<Bool>.OCombine.Publisher {
return .init(self.output.map(predicate))
}
public func count() -> Optional<Int>.OCombine.Publisher {
return .init(self.output.map { _ in 1 })
}
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
precondition(count >= 0, "count must not be negative")
return .init(self.output.flatMap { count == 0 ? $0 : nil })
}
public func drop(
while predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(self.output.flatMap { predicate($0) ? nil : $0 })
}
public func first() -> Optional<Output>.OCombine.Publisher {
return self
}
public func first(
where predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { predicate($0) ? $0 : nil })
}
public func last() -> Optional<Output>.OCombine.Publisher {
return self
}
public func last(
where predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { predicate($0) ? $0 : nil })
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { isIncluded($0) ? $0 : nil })
}
public func ignoreOutput() -> Empty<Output, Failure> {
return .init()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(output.map(transform))
}
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
precondition(index >= 0, "index must not be negative")
return .init(output.flatMap { index == 0 ? $0 : nil })
}
public func output<RangeExpression: Swift.RangeExpression>(
in range: RangeExpression
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
let range = range.relative(to: 0 ..< Int.max)
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
// I don't know why, but Combine has this precondition
precondition(range.upperBound < .max - 1)
return .init(
output.flatMap { (range.lowerBound == 0 && range.upperBound != 0) ? $0 : nil }
)
}
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
precondition(maxLength >= 0, "maxLength must not be negative")
return .init(output.flatMap { maxLength > 0 ? $0 : nil })
}
public func prefix(
while predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { predicate($0) ? $0 : nil })
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Optional<Accumulator>.OCombine.Publisher {
return .init(output.map { nextPartialResult(initialResult, $0) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(output.map { nextPartialResult(initialResult, $0) })
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return self
}
public func replaceError(with output: Output) -> Optional<Output>.OCombine.Publisher {
return self
}
public func replaceEmpty(with output: Output) -> Just<Output> {
return .init(self.output ?? output)
}
public func retry(_ times: Int) -> Optional<Output>.OCombine.Publisher {
return self
}
}
@@ -0,0 +1,178 @@
//
// Publishers.AllSatisfy.swift
//
//
// Created by Sergej Jaskiewicz on 09.10.2019.
//
extension Publisher {
/// Publishes a single Boolean value that indicates whether all received elements pass
/// a given predicate.
///
/// When this publisher receives an element, it runs the predicate against
/// the element. If the predicate returns `false`, the publisher produces a `false`
/// value and finishes. If the upstream publisher finishes normally, this publisher
/// produces a `true` value and finishes.
/// As a `reduce`-style operator, this publisher produces at most one value.
/// Backpressure note: Upon receiving any request greater than zero, this publisher
/// requests unlimited elements from the upstream publisher.
///
/// - Parameter predicate: A closure that evaluates each received element.
/// Return `true` to continue, or `false` to cancel the upstream and complete.
/// - Returns: A publisher that publishes a Boolean value that indicates whether
/// all received elements pass a given predicate.
public func allSatisfy(
_ predicate: @escaping (Output) -> Bool
) -> Publishers.AllSatisfy<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes a single Boolean value that indicates whether all received elements pass
/// a given error-throwing predicate.
///
/// When this publisher receives an element, it runs the predicate against
/// the element. If the predicate returns `false`, the publisher produces a `false`
/// value and finishes. If the upstream publisher finishes normally, this publisher
/// produces a `true` value and finishes. If the predicate throws an error,
/// the publisher fails, passing the error to its downstream.
/// As a `reduce`-style operator, this publisher produces at most one value.
/// Backpressure note: Upon receiving any request greater than zero, this publisher
/// requests unlimited elements from the upstream publisher.
///
/// - Parameter predicate: A closure that evaluates each received element.
/// Return `true` to continue, or `false` to cancel the upstream and complete.
/// The closure may throw, in which case the publisher cancels the upstream
/// publisher and fails with the thrown error.
/// - Returns: A publisher that publishes a Boolean value that indicates whether
/// all received elements pass a given predicate.
public func tryAllSatisfy(
_ predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryAllSatisfy<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that publishes a single Boolean value that indicates whether
/// all received elements pass a given predicate.
public struct AllSatisfy<Upstream: Publisher>: Publisher {
public typealias Output = Bool
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that evaluates each received element.
///
/// Return `true` to continue, or `false` to cancel the upstream and finish.
public let predicate: (Upstream.Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
/// A publisher that publishes a single Boolean value that indicates whether
/// all received elements pass a given error-throwing predicate.
public struct TryAllSatisfy<Upstream: Publisher>: Publisher {
public typealias Output = Bool
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that evaluates each received element.
///
/// Return `true` to continue, or `false` to cancel the upstream and complete.
/// The closure may throw, in which case the publisher cancels the upstream
/// publisher and fails with the thrown error.
public let predicate: (Upstream.Output) throws -> Bool
public init(upstream: Upstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Error, Downstream.Input == Bool
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
extension Publishers.AllSatisfy {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Bool,
Upstream.Failure,
(Upstream.Output) -> Bool>
where Downstream.Input == Output, Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Upstream.Output) -> Bool) {
super.init(downstream: downstream, initial: true, reduce: predicate)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
if !reduce(newValue) {
result = false
return .finished
}
return .continue
}
override var description: String { return "AllSatisfy" }
}
}
extension Publishers.TryAllSatisfy {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Bool,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Downstream.Input == Output, Downstream.Failure == Error
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
super.init(downstream: downstream, initial: true, reduce: predicate)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
do {
if try !reduce(newValue) {
result = false
return .finished
}
} catch {
return .failure(error)
}
return .continue
}
override var description: String { return "TryAllSatisfy" }
}
}
@@ -0,0 +1,191 @@
//
// Publishers.Autoconnect.swift
//
//
// Created by Sergej Jaskiewicz on 18/09/2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension ConnectablePublisher {
/// Automates the process of connecting or disconnecting from this connectable
/// publisher.
///
/// Use `autoconnect()` to simplify working with `ConnectablePublisher` instances,
/// such as those created with `makeConnectable()`.
///
/// let autoconnectedPublisher = somePublisher
/// .makeConnectable()
/// .autoconnect()
/// .subscribe(someSubscriber)
///
/// - Returns: A publisher which automatically connects to its upstream connectable
/// publisher.
public func autoconnect() -> Publishers.Autoconnect<Self> {
return .init(upstream: self)
}
}
extension Publishers {
/// A publisher that automatically connects and disconnects from this connectable
/// publisher.
public class Autoconnect<Upstream: ConnectablePublisher>: Publisher {
// NOTE: This class has been audited for thread safety
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
private enum State {
case disconnected
case connected(refcount: Int, connection: Cancellable)
}
/// The publisher from which this publisher receives elements.
public final let upstream: Upstream
private let lock = UnfairLock.allocate()
private var state = State.disconnected
public init(upstream: Upstream) {
self.upstream = upstream
}
deinit {
lock.deallocate()
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(parent: self, downstream: subscriber)
lock.lock()
switch state {
case let .connected(refcount, connection):
state = .connected(refcount: refcount + 1, connection: connection)
lock.unlock()
upstream.subscribe(inner)
case .disconnected:
lock.unlock()
upstream.subscribe(inner)
let connection = upstream.connect()
lock.lock()
state = .connected(refcount: 1, connection: connection)
lock.unlock()
}
}
fileprivate func willCancel() {
lock.lock()
switch state {
case let .connected(refcount, connection):
if refcount <= 1 {
self.state = .disconnected
lock.unlock()
connection.cancel()
} else {
state = .connected(refcount: refcount - 1, connection: connection)
lock.unlock()
}
case .disconnected:
lock.unlock()
}
}
}
}
extension Publishers.Autoconnect {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
fileprivate let combineIdentifier = CombineIdentifier()
private let parent: Publishers.Autoconnect<Upstream>
private let downstream: Downstream
fileprivate init(parent: Publishers.Autoconnect<Upstream>,
downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
fileprivate func receive(subscription: Subscription) {
let sideEffectSubscription = SideEffectSubscription(subscription,
parent: parent)
downstream.receive(subscription: sideEffectSubscription)
}
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
fileprivate var description: String { return "Autoconnect" }
fileprivate var customMirror: Mirror {
let children: [Mirror.Child] = [
("parent", parent),
("downstream", downstream)
]
return Mirror(self, children: children)
}
fileprivate var playgroundDescription: Any { return description }
}
private struct SideEffectSubscription
: Subscription,
CustomStringConvertible,
CustomPlaygroundDisplayConvertible
{
private let parent: Publishers.Autoconnect<Upstream>
private let upstreamSubscription: Subscription
fileprivate init(_ upstreamSubscription: Subscription,
parent: Publishers.Autoconnect<Upstream>) {
self.parent = parent
self.upstreamSubscription = upstreamSubscription
}
fileprivate func request(_ demand: Subscribers.Demand) {
upstreamSubscription.request(demand)
}
fileprivate func cancel() {
parent.willCancel()
upstreamSubscription.cancel()
}
fileprivate var combineIdentifier: CombineIdentifier {
return upstreamSubscription.combineIdentifier
}
fileprivate var description: String {
return String(describing: upstreamSubscription)
}
var playgroundDescription: Any {
return description
}
}
}
@@ -0,0 +1,183 @@
//
// Publishers.Breakpoint.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Raises a debugger signal when a provided closure needs to stop the process in
/// the debugger.
///
/// When any of the provided closures returns `true`, this publisher raises
/// the `SIGTRAP` signal to stop the process in the debugger.
/// Otherwise, this publisher passes through values and completions as-is.
///
/// - Parameters:
/// - receiveSubscription: A closure that executes when when the publisher receives
/// a subscription. Return `true` from this closure to raise `SIGTRAP`, or `false`
/// to continue.
/// - receiveOutput: A closure that executes when when the publisher receives
/// a value. Return `true` from this closure to raise `SIGTRAP`, or `false`
/// to continue.
/// - receiveCompletion: A closure that executes when when the publisher receives
/// a completion. Return `true` from this closure to raise `SIGTRAP`, or `false`
/// to continue.
/// - Returns: A publisher that raises a debugger signal when one of the provided
/// closures returns `true`.
public func breakpoint(
receiveSubscription: ((Subscription) -> Bool)? = nil,
receiveOutput: ((Output) -> Bool)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Bool)? = nil
) -> Publishers.Breakpoint<Self> {
return .init(upstream: self,
receiveSubscription: receiveSubscription,
receiveOutput: receiveOutput,
receiveCompletion: receiveCompletion)
}
/// Raises a debugger signal upon receiving a failure.
///
/// When the upstream publisher fails with an error, this publisher raises
/// the `SIGTRAP` signal, which stops the process in the debugger.
/// Otherwise, this publisher passes through values and completions as-is.
///
/// - Returns: A publisher that raises a debugger signal upon receiving a failure.
public func breakpointOnError() -> Publishers.Breakpoint<Self> {
return breakpoint { completion in
switch completion {
case .finished:
return false
case .failure:
return true
}
}
}
}
extension Publishers {
/// A publisher that raises a debugger signal when a provided closure needs to stop
/// the process in the debugger.
///
/// When any of the provided closures returns `true`, this publisher raises
/// the `SIGTRAP` signal to stop the process in the debugger.
/// Otherwise, this publisher passes through values and completions as-is.
public struct Breakpoint<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that executes when the publisher receives a subscription, and can
/// raise a debugger signal by returning a `true` Boolean value.
public let receiveSubscription: ((Subscription) -> Bool)?
/// A closure that executes when the publisher receives output from the upstream
/// publisher, and can raise a debugger signal by returning a `true` Boolean
/// value.
public let receiveOutput: ((Upstream.Output) -> Bool)?
/// A closure that executes when the publisher receives completion, and can raise
/// a debugger signal by returning a `true` Boolean value.
public let receiveCompletion:
((Subscribers.Completion<Upstream.Failure>) -> Bool)?
/// Creates a breakpoint publisher with the provided upstream publisher and
/// breakpoint-raising closures.
///
/// - Parameters:
/// - upstream: The publisher from which this publisher receives elements.
/// - receiveSubscription: A closure that executes when the publisher receives
/// a subscription, and can raise a debugger signal by returning a `true`
/// Boolean value.
/// - receiveOutput: A closure that executes when the publisher receives output
/// from the upstream publisher, and can raise a debugger signal by returning
/// a `true` Boolean value.
/// - receiveCompletion: A closure that executes when the publisher receives
/// completion, and can raise a debugger signal by returning a `true` Boolean
/// value.
public init(
upstream: Upstream,
receiveSubscription: ((Subscription) -> Bool)? = nil,
receiveOutput: ((Upstream.Output) -> Bool)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Bool)? = nil
) {
self.upstream = upstream
self.receiveSubscription = receiveSubscription
self.receiveOutput = receiveOutput
self.receiveCompletion = receiveCompletion
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(self, downstream: subscriber))
}
}
}
extension Publishers.Breakpoint {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let breakpoint: Publishers.Breakpoint<Upstream>
let combineIdentifier = CombineIdentifier()
init(_ breakpoint: Publishers.Breakpoint<Upstream>,
downstream: Downstream) {
self.downstream = downstream
self.breakpoint = breakpoint
}
func receive(subscription: Subscription) {
if breakpoint.receiveSubscription?(subscription) == true {
__stopInDebugger()
}
downstream.receive(subscription: subscription)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
if breakpoint.receiveOutput?(input) == true {
__stopInDebugger()
}
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if breakpoint.receiveCompletion?(completion) == true {
__stopInDebugger()
}
downstream.receive(completion: completion)
}
var description: String { return "Breakpoint" }
var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(
("upstream", breakpoint.upstream)
)
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,85 @@
//
// Publishers.Collect.swift
//
//
// Created by Sergej Jaskiewicz on 09.10.2019.
//
extension Publisher {
/// Collects all received elements, and emits a single array of the collection when
/// the upstream publisher finishes.
///
/// If the upstream publisher fails with an error, this publisher forwards the error
/// to the downstream receiver instead of sending its output.
/// This publisher requests an unlimited number of elements from the upstream
/// publisher. It only sends the collected array to its downstream after a request
/// whose demand is greater than 0 items.
/// Note: This publisher uses an unbounded amount of memory to store the received
/// values.
///
/// - Returns: A publisher that collects all received items and returns them as
/// an array upon completion.
public func collect() -> Publishers.Collect<Self> {
return .init(upstream: self)
}
}
extension Publishers {
/// A publisher that buffers items.
public struct Collect<Upstream: Publisher>: Publisher {
public typealias Output = [Upstream.Output]
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
public init(upstream: Upstream) {
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Downstream.Input == [Upstream.Output]
{
upstream.subscribe(Inner(downstream: subscriber))
}
}
}
extension Publishers.Collect: Equatable where Upstream: Equatable {}
extension Publishers.Collect {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
[Upstream.Output],
Upstream.Failure,
Void>
where Downstream.Input == [Upstream.Output],
Downstream.Failure == Upstream.Failure
{
fileprivate init(downstream: Downstream) {
super.init(downstream: downstream, initial: [], reduce: ())
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
result!.append(newValue)
return .continue
}
override var description: String {
return "Collect"
}
override var customMirror: Mirror {
let children: CollectionOfOne<Mirror.Child> = .init(("count", result!.count))
return Mirror(self, children: children)
}
}
}
@@ -0,0 +1,168 @@
//
// Publishers.CompactMap.swift
//
//
// Created by Sergej Jaskiewicz on 11.07.2019.
//
extension Publisher {
/// Calls a closure with each received element and publishes any returned
/// optional that has a value.
///
/// - Parameter transform: A closure that receives a value and returns
/// an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling
/// the transform closure.
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult?
) -> Publishers.CompactMap<Self, ElementOfResult> {
return .init(upstream: self, transform: transform)
}
/// Calls an error-throwing closure with each received element and publishes
/// any returned optional that has a value.
///
/// If the closure throws an error, the publisher cancels the upstream and sends
/// the thrown error to the downstream receiver as a `Failure`.
///
/// - Parameter transform: an error-throwing closure that receives a value
/// and returns an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling
/// the `transform` closure.
public func tryCompactMap<ElementOfResult>(
_ transform: @escaping (Output) throws -> ElementOfResult?
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
return .init(upstream: self, transform: transform)
}
}
extension Publishers.CompactMap {
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult?
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
return .init(upstream: upstream,
transform: { self.transform($0).flatMap(transform) })
}
public func map<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
return .init(upstream: upstream,
transform: { self.transform($0).map(transform) })
}
}
extension Publishers.TryCompactMap {
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) throws -> ElementOfResult?
) -> Publishers.TryCompactMap<Upstream, ElementOfResult> {
return .init(upstream: upstream,
transform: { try self.transform($0).flatMap(transform) })
}
}
extension Publishers {
/// A publisher that republishes all non-`nil` results of calling a closure
/// with each received element.
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that receives values from the upstream publisher
/// and returns optional values.
public let transform: (Upstream.Output) -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
}
}
/// A publisher that republishes all non-`nil` results of calling an error-throwing
/// closure with each received element.
public struct TryCompactMap<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// An error-throwing closure that receives values from the upstream publisher
/// and returns optional values.
///
/// If this closure throws an error, the publisher fails.
public let transform: (Upstream.Output) throws -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) throws -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
upstream.subscribe(Inner(downstream: subscriber, filter: transform))
}
}
}
extension Publishers.CompactMap {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Output,
Upstream.Failure,
(Upstream.Output) -> Output?>
where Downstream.Failure == Upstream.Failure, Downstream.Input == Output
{
// NOTE: This class has been audited for thread safety
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Output?, Downstream.Failure> {
return .continue(filter(newValue))
}
override var description: String { return "CompactMap" }
}
}
extension Publishers.TryCompactMap {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Output,
Upstream.Failure,
(Upstream.Output) throws -> Output?>
where Downstream.Failure == Error, Downstream.Input == Output
{
// NOTE: This class has been audited for thread safety
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Output?, Error> {
do {
return try .continue(filter(newValue))
} catch {
return .failure(error)
}
}
override var description: String { return "TryCompactMap" }
}
}
@@ -0,0 +1,246 @@
//
// Publishers.Comparison.swift
// OpenCombine
//
// Created by Ilija Puaca on 22/7/19.
//
extension Publisher where Output: Comparable {
/// Publishes the minimum value received from the upstream publisher, after it
/// finishes.
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Returns: A publisher that publishes the minimum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func min() -> Publishers.Comparison<Self> {
return max(by: >)
}
/// Publishes the maximum value received from the upstream publisher, after it
/// finishes.
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Returns: A publisher that publishes the maximum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func max() -> Publishers.Comparison<Self> {
return max(by: <)
}
}
extension Publisher {
/// Publishes the minimum value received from the upstream publisher, after it
/// finishes.
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
/// `true` if they are in increasing order.
/// - Returns: A publisher that publishes the minimum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func min(
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
) -> Publishers.Comparison<Self> {
return max(by: { areInIncreasingOrder($1, $0) })
}
/// Publishes the minimum value received from the upstream publisher, using the
/// provided error-throwing closure to order the items.
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
/// and returns `true` if they are in increasing order. If this closure throws, the
/// publisher terminates with a `Failure`.
/// - Returns: A publisher that publishes the minimum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func tryMin(
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
) -> Publishers.TryComparison<Self> {
return tryMax(by: { try areInIncreasingOrder($1, $0) })
}
/// Publishes the maximum value received from the upstream publisher, using the
/// provided ordering closure.
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
///
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
/// `true` if they are in increasing order.
/// - Returns: A publisher that publishes the maximum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func max(
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
) -> Publishers.Comparison<Self> {
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
}
/// Publishes the maximum value received from the upstream publisher, using the
/// provided error-throwing closure to order the items.
///
/// After this publisher receives a request for more than 0 items, it requests
/// unlimited items from its upstream publisher.
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
/// and returns `true` if they are in increasing order. If this closure throws, the
/// publisher terminates with a `Failure`.
/// - Returns: A publisher that publishes the maximum value received from the upstream
/// publisher, after the upstream publisher finishes.
public func tryMax(
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
) -> Publishers.TryComparison<Self> {
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
}
}
extension Publishers {
/// A publisher that republishes items from another publisher only if each new item is
/// in increasing order from the previously-published item.
public struct Comparison<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that receives two elements and returns `true` if they are in
/// increasing order.
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) -> Bool
public init(
upstream: Upstream,
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
) {
self.upstream = upstream
self.areInIncreasingOrder = areInIncreasingOrder
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber,
areInIncreasingOrder: areInIncreasingOrder)
upstream.subscribe(inner)
}
}
/// A publisher that republishes items from another publisher only if each new item is
/// in increasing order from the previously-published item, and fails if the ordering
/// logic throws an error.
public struct TryComparison<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that receives two elements and returns `true` if they are in
/// increasing order.
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) throws -> Bool
public init(
upstream: Upstream,
areInIncreasingOrder:
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
) {
self.upstream = upstream
self.areInIncreasingOrder = areInIncreasingOrder
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
let inner = Inner(downstream: subscriber,
areInIncreasingOrder: areInIncreasingOrder)
upstream.subscribe(inner)
}
}
}
extension Publishers.Comparison {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output, Upstream.Output) -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
fileprivate init(
downstream: Downstream,
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
) {
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
if let result = self.result {
if reduce(result, newValue) {
self.result = newValue
}
} else {
self.result = newValue
}
return .continue
}
override var description: String {
return "Comparison"
}
}
}
extension Publishers.TryComparison {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output, Upstream.Output) throws -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
{
fileprivate init(
downstream: Downstream,
areInIncreasingOrder:
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
) {
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
do {
if let result = self.result {
if try reduce(result, newValue) {
self.result = newValue
}
} else {
self.result = newValue
}
return .continue
} catch {
return .failure(error)
}
}
override var description: String {
return "TryComparison"
}
}
}
@@ -0,0 +1,250 @@
//
// Publishers.Concatenate.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
extension Publisher {
/// Prefixes a `Publisher`'s output with the specified sequence.
///
/// - Parameter elements: The elements to publish before this 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 `Publisher`'s output with the specified sequence.
///
/// - 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 this publishers output with the elements emitted by the given publisher.
///
/// The resulting publisher doesnt emit any elements until the prefixing publisher
/// finishes.
///
/// - 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)
}
/// Append a `Publisher`'s output with the specified sequence.
public func append(
_ elements: Output...
) -> Publishers.Concatenate<Self, Publishers.Sequence<[Output], Failure>> {
return append(elements)
}
/// Appends a `Publisher`'s output with the specified sequence.
public func append<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Concatenate<Self, Publishers.Sequence<Elements, Failure>>
where Output == Elements.Element
{
return append(.init(sequence: elements))
}
/// Appends this publishers output with the elements emitted by the given publisher.
///
/// This 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 prefixing publisher does not publish
/// the provided publishers elements.
///
/// - 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 anothe
/// publisher.
public struct Concatenate<Prefix: Publisher, Suffix: Publisher>: Publisher
where Prefix.Failure == Suffix.Failure, Prefix.Output == Suffix.Output
{
public typealias Output = Suffix.Output
public typealias Failure = Suffix.Failure
/// The publisher to republish, in its entirety, before republishing elements from
/// `suffix`.
public let prefix: Prefix
/// The publisher to republish only after `prefix` finishes.
public let suffix: Suffix
public init(prefix: Prefix, suffix: Suffix) {
self.prefix = prefix
self.suffix = suffix
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Suffix.Failure == Downstream.Failure, Suffix.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, suffix: suffix)
prefix.subscribe(inner)
subscriber.receive(subscription: inner)
}
}
}
extension Publishers.Concatenate: Equatable where Prefix: Equatable, Suffix: Equatable {}
extension Publishers.Concatenate {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Suffix.Output, Downstream.Failure == Suffix.Failure
{
typealias Input = Suffix.Output
typealias Failure = Suffix.Failure
private let downstream: Downstream
private let suffix: Suffix
private var prefixFinished = false
private var demand = Subscribers.Demand.none
private var upstream: Subscription?
private var expectedSubscriptions = 2
private let lock = UnfairLock.allocate()
// ??? This lock is non-recursive in Combine, but it should be!
// (FB7404824 if Apple folks are watching)
private let downstreamLock = UnfairLock.allocate()
fileprivate init(downstream: Downstream, suffix: Suffix) {
self.downstream = downstream
self.suffix = suffix
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard upstream == nil, expectedSubscriptions > 0 else {
lock.unlock()
subscription.cancel()
return
}
upstream = subscription
expectedSubscriptions -= 1
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receive(completion: Subscribers.Completion<Failure>) {
// Reading prefixFinished should be locked. Combine doesn't lock here.
if prefixFinished {
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
return
}
guard case .finished = completion else {
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
return
}
prefixFinished = true // Should be locked as well?
lock.lock()
upstream = nil
lock.unlock()
suffix.subscribe(self)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
self.demand += demand
guard let subscription = upstream else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard let subscription = upstream else {
lock.unlock()
return
}
upstream = nil
lock.unlock()
subscription.cancel()
}
var description: String { return "Concatenate" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("upstreamSubscription", upstream as Any),
("suffix", suffix),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,235 @@
//
// Publishers.Contains.swift
//
//
// Created by Sergej Jaskiewicz on 09.10.2019.
//
extension Publisher where Output: Equatable {
/// Publishes a Boolean value upon receiving an element equal to the argument.
///
/// The contains publisher consumes all received elements until the upstream publisher
/// produces a matching element. At that point, it emits `true` and finishes normally.
/// If the upstream finishes normally without producing a matching element,
/// this publisher emits `false`, then finishes.
///
/// - Parameter output: An element to match against.
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
/// publisher emits a matching value.
public func contains(_ output: Output) -> Publishers.Contains<Self> {
return .init(upstream: self, output: output)
}
}
extension Publisher {
/// Publishes a Boolean value upon receiving an element that satisfies the predicate
/// closure.
///
/// This operator consumes elements produced from the upstream publisher until
/// the upstream publisher produces a matching element.
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether the element satisfies the closures
/// comparison logic.
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
/// publisher emits a matching value.
public func contains(
where predicate: @escaping (Output) -> Bool
) -> Publishers.ContainsWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes a Boolean value upon receiving an element that satisfies
/// the throwing predicate closure.
///
/// This operator consumes elements produced from the upstream publisher until
/// the upstream publisher produces a matching element. If the closure throws,
/// the stream fails with an error.
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether the element satisfies the closures
/// comparison logic.
/// - Returns: A publisher that emits the Boolean value `true` when the upstream
/// publisher emits a matching value.
public func tryContains(
where predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryContainsWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that emits a Boolean value when a specified element is received from
/// its upstream publisher.
public struct Contains<Upstream: Publisher>: Publisher
where Upstream.Output: Equatable
{
public typealias Output = Bool
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The element to scan for in the upstream publisher.
public let output: Upstream.Output
public init(upstream: Upstream, output: Upstream.Output) {
self.upstream = upstream
self.output = output
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
{
upstream.subscribe(Inner(downstream: subscriber, output: output))
}
}
/// A publisher that emits a Boolean value upon receiving an element that satisfies
/// the predicate closure.
public struct ContainsWhere<Upstream: Publisher>: Publisher {
public typealias Output = Bool
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether the publisher should consider an element
/// as a match.
public let predicate: (Upstream.Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
/// A publisher that emits a Boolean value upon receiving an element that satisfies
/// the throwing predicate closure.
public struct TryContainsWhere<Upstream: Publisher>: Publisher {
public typealias Output = Bool
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether this publisher should
/// emit a `true` element.
public let predicate: (Upstream.Output) throws -> Bool
public init(upstream: Upstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Error, Downstream.Input == Bool
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
extension Publishers.Contains {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream, Upstream.Output, Bool, Upstream.Failure, Void>
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
{
private let output: Upstream.Output
fileprivate init(downstream: Downstream, output: Upstream.Output) {
self.output = output
super.init(downstream: downstream, initial: false, reduce: ())
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
if newValue == output {
result = true
return .finished
}
return .continue
}
override var description: String { return "Contains" }
}
}
extension Publishers.Contains : Equatable where Upstream: Equatable {}
extension Publishers.ContainsWhere {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output, Bool,
Upstream.Failure,
(Upstream.Output) -> Bool>
where Upstream.Failure == Downstream.Failure, Downstream.Input == Bool
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Upstream.Output) -> Bool) {
super.init(downstream: downstream, initial: false, reduce: predicate)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
if reduce(newValue) {
result = true
return .finished
}
return .continue
}
override var description: String { return "ContainsWhere" }
}
}
extension Publishers.TryContainsWhere {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output, Bool,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Downstream.Failure == Error, Downstream.Input == Bool
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
super.init(downstream: downstream, initial: false, reduce: predicate)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
do {
if try reduce(newValue) {
result = true
return .finished
}
} catch {
return .failure(error)
}
return .continue
}
override var description: String { return "TryContainsWhere" }
}
}
@@ -0,0 +1,77 @@
//
// Publishers.Count.swift
//
//
// Created by Joseph Spadafora on 6/25/19.
//
extension Publisher {
/// Publishes the number of elements received from the upstream publisher.
///
/// - Returns: A publisher that consumes all elements until the upstream publisher
/// finishes, then emits a single value with the total number of elements received.
public func count() -> Publishers.Count<Self> {
return Publishers.Count(upstream: self)
}
}
extension Publishers {
/// A publisher that publishes the number of elements received
/// from the upstream publisher.
public struct Count<Upstream: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Int
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream) {
self.upstream = upstream
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Downstream.Input == Output
{
upstream.subscribe(Inner(downstream: subscriber))
}
}
}
extension Publishers.Count: Equatable where Upstream: Equatable {}
extension Publishers.Count {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream, Upstream.Output, Int, Failure, Void>
where Downstream.Input == Int,
Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream) {
super.init(downstream: downstream, initial: 0, reduce: ())
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
result! += 1
return .continue
}
override var description: String { return "Count" }
}
}
@@ -1,109 +0,0 @@
//
// Publishers.Decode.swift
//
//
// Created by Joseph Spadafora on 6/21/19.
//
extension Publishers {
public struct Decode<Upstream, Output, Coder>: Publisher
where Upstream: Publisher,
Output: Decodable,
Coder: TopLevelDecoder,
Upstream.Output == Coder.Input
{
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
public let upstream: Upstream
private let _decoder: Coder
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decoder = decoder
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
let decodeSubscriber = _Decode<Upstream, SubscriberType, Coder>(
downstream: subscriber,
decoder: _decoder
)
upstream.receive(subscriber: 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,229 @@
//
// Publishers.Delay.swift
// OpenCombine
//
// Created by Евгений Богомолов on 07/09/2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Delays delivery of all output to the downstream receiver by a specified amount
/// of time on a particular scheduler.
///
/// The delay affects the delivery of elements and completion, but not of the original
/// subscription.
///
/// - Parameters:
/// - interval: The amount of time to delay.
/// - tolerance: The allowed tolerance in firing delayed events.
/// - scheduler: The scheduler to deliver the delayed events.
/// - Returns: A publisher that delays delivery of elements and completion to
/// the downstream receiver.
public func delay<Context: Scheduler>(
for interval: Context.SchedulerTimeType.Stride,
tolerance: Context.SchedulerTimeType.Stride? = nil,
scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.Delay<Self, Context> {
return .init(upstream: self,
interval: interval,
tolerance: tolerance ?? scheduler.minimumTolerance,
scheduler: scheduler,
options: options)
}
}
extension Publishers {
/// A publisher that delays delivery of elements and completion
/// to the downstream receiver.
public struct Delay<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// The amount of time to delay.
public let interval: Context.SchedulerTimeType.Stride
/// The allowed tolerance in firing delayed events.
public let tolerance: Context.SchedulerTimeType.Stride
/// The scheduler to deliver the delayed events.
public let scheduler: Context
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
interval: Context.SchedulerTimeType.Stride,
tolerance: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions? = nil)
{
self.upstream = upstream
self.interval = interval
self.tolerance = tolerance
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(self, downstream: subscriber))
}
}
}
extension Publishers.Delay {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
// NOTE: This class has been audited for thread safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
fileprivate typealias Delay = Publishers.Delay<Upstream, Context>
private enum State {
case ready(Delay, Downstream)
case subscribed(Delay, Downstream, Subscription)
case terminal
}
private let lock = UnfairLock.allocate()
private var state: State
private let downstreamLock = UnfairLock.allocate()
fileprivate init(_ publisher: Delay, downstream: Downstream) {
state = .ready(publisher, downstream)
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
private func schedule(_ delay: Delay,
immediate: Bool,
work: @escaping () -> Void) {
if immediate {
delay.scheduler.schedule(options: delay.options, work)
return
}
delay
.scheduler
.schedule(after: delay.scheduler.now.advanced(by: delay.interval),
tolerance: delay.tolerance,
options: delay.options,
work)
}
func receive(subscription: Subscription) {
lock.lock()
guard case let .ready(delay, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(delay, downstream, subscription)
lock.unlock()
schedule(delay, immediate: true) { [weak self] in
self?.scheduledReceive(subscription: subscription)
}
}
private func scheduledReceive(subscription: Subscription) {
lock.lock()
guard case let .subscribed(_, downstream, _) = state else {
lock.unlock()
return
}
lock.unlock()
downstreamLock.lock()
downstream.receive(subscription: self)
downstreamLock.unlock()
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(delay, downstream, _) = state else {
lock.unlock()
return .none
}
lock.unlock()
schedule(delay, immediate: false) { [weak self] in
self?.scheduledReceive(input, downstream: downstream)
}
return .none
}
private func scheduledReceive(_ input: Upstream.Output, downstream: Downstream) {
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
guard newDemand > 0 else {
return
}
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(newDemand)
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case let .subscribed(delay, downstream, _) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
schedule(delay, immediate: false) { [weak self] in
self?.scheduledReceive(completion: completion, downstream: downstream)
}
}
private func scheduledReceive(completion: Subscribers.Completion<Failure>,
downstream: Downstream) {
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subscription.cancel()
}
}
}
@@ -0,0 +1,147 @@
//
// Publishers.Drop.swift
//
//
// Created by Sven Weidauer on 03.10.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Omits the specified number of elements before republishing subsequent elements.
///
/// - Parameter count: The number of elements to omit.
/// - Returns: A publisher that does not republish the first `count` elements.
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self> {
return .init(upstream: self, count: count)
}
}
extension Publishers {
/// A publisher that omits a specified number of elements before republishing
/// later elements.
public struct Drop<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The number of elements to drop.
public let count: Int
public init(upstream: Upstream, count: Int) {
self.upstream = upstream
self.count = count
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, count: count)
upstream.subscribe(inner)
subscriber.receive(subscription: inner)
}
}
}
extension Publishers.Drop: Equatable where Upstream: Equatable {}
extension Publishers.Drop {
private final class Inner<Downstream: Subscriber>
: Subscription,
Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
// NOTE: This class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let lock = UnfairLock.allocate()
private var subscription: Subscription?
private var pendingDemand = Subscribers.Demand.none
private var count: Int
fileprivate init(downstream: Downstream, count: Int) {
self.downstream = downstream
self.count = count
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard self.subscription == nil else {
lock.unlock()
subscription.cancel()
return
}
self.subscription = subscription
precondition(count >= 0, "count must not be negative")
let demandToRequestFromUpstream = pendingDemand + count
lock.unlock()
if demandToRequestFromUpstream > 0 {
subscription.request(demandToRequestFromUpstream)
}
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
// Combine doesn't lock here!
if count > 0 {
count -= 1
return .none
}
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
// Combine doesn't lock here!
subscription = nil
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard let subscription = self.subscription else {
self.pendingDemand += demand
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
// Combine doesn't lock here!
subscription?.cancel()
subscription = nil
}
var description: String { return "Drop" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -5,6 +5,44 @@
// Created by Sergej Jaskiewicz on 16.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
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 .init(upstream: self, predicate: predicate)
}
/// Omits elements from the upstream publisher until an error-throwing closure returns
/// false, before republishing all remaining elements.
///
/// If the predicate closure throws, the publisher fails with an error.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the 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
@@ -26,11 +64,10 @@ extension Publishers {
self.predicate = predicate
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.receive(subscriber: inner)
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
@@ -53,152 +90,252 @@ extension Publishers {
self.predicate = predicate
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, SubscriberType.Failure == Error
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Error
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.receive(subscriber: inner)
}
}
}
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
CustomStringConvertible,
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 demand: Subscribers.Demand = .none
init(downstream: Downstream, predicate: @escaping Predicate) {
self.predicate = predicate
super.init(downstream: downstream)
}
var description: String { return "DropWhile" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
// NOTE: until the predicate returns false, we will ask the upstream publisher
// for elements one by one, no matter how much elements the downstream subscriber
// requests.
//
// 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.
subscription.request(.max(1))
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard let predicate = self.predicate else {
return downstream.receive(input)
}
switch predicate(input) {
case .success(true):
// See the NOTE above to understand why we return .max(1)
return .max(1)
case .success(false):
// Okay, we hit the first element not satisfying the predicate,
// from now on we just republish the values to the downstream.
self.predicate = nil
// The demand that the downstream has requested has been accumulated in the
// `demand` property. Now it's time to pay the debt.
//
// Subtracting 1 for the current value.
return demand + downstream.receive(input) - 1
case .failure(let error):
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func request(_ demand: Subscribers.Demand) {
if predicate == nil {
// If predicate is nil, that means that we have already received a value
// that doesn't satisfy the predicate, hence we're in the state where we
// just forward each request to the upstream.
upstreamSubscription?.request(demand)
} else {
// Otherwise, as mentioned in the NOTE above, we accumulate all the demand
// requested by the downstream until the predicate returns false.
self.demand += demand
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
extension Publishers.DropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
Subscriber
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
// 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>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
predicate = nil
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
predicate = nil
lock.unlock()
subscription.cancel()
}
var description: String { return "DropWhile" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryDropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
Subscriber
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion.eraseError())
// 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
}
}
}
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)
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>) {
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())
}
}
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,56 +0,0 @@
//
// Publishers.Empty.swift
//
//
// Created by Sergej Jaskiewicz on 16.06.2019.
//
extension Publishers {
/// A publisher that never publishes any values, and optionally finishes immediately.
///
/// You can create a Never publisher one which never sends values and never
/// finishes or fails with the initializer `Empty(completeImmediately: false)`.
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
/// Creates an empty publisher.
///
/// - Parameter completeImmediately: A Boolean value that indicates whether
/// the publisher should immediately finish.
public init(completeImmediately: Bool = true) {
self.completeImmediately = completeImmediately
}
/// Creates an empty publisher with the given completion behavior and output and
/// failure types.
///
/// Use this initializer to connect the empty publisher to subscribers or other
/// publishers that have specific output and failure types.
/// - Parameters:
/// - completeImmediately: A Boolean value that indicates whether the publisher
/// should immediately finish.
/// - outputType: The output type exposed by this publisher.
/// - failureType: The failure type exposed by this publisher.
public init(completeImmediately: Bool = true,
outputType: Output.Type,
failureType: Failure.Type) {
self.init(completeImmediately: completeImmediately)
}
/// A Boolean value that indicates whether the publisher immediately sends
/// a completion.
///
/// If `true`, the publisher finishes immediately after sending a subscription
/// to the subscriber. If `false`, it never completes.
public let completeImmediately: Bool
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
if completeImmediately {
subscriber.receive(completion: .finished)
}
}
}
}
@@ -1,109 +0,0 @@
//
// Publishers.Encode.swift
//
//
// Created by Joseph Spadafora on 6/22/19.
//
extension Publishers {
public struct Encode<Upstream, Coder>: Publisher
where Upstream: Publisher,
Coder: TopLevelEncoder,
Upstream.Output: Encodable
{
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
/// The kind of values published by this publisher.
public typealias Output = Coder.Output
public let upstream: Upstream
private let encoder: Coder
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self.encoder = encoder
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
let encodeSubscriber = _Encode<Upstream, SubscriberType, Coder>(
downstream: subscriber,
encoder: encoder
)
upstream.receive(subscriber: encodeSubscriber)
}
}
}
private final class _Encode<Upstream: Publisher,
Downstream: Subscriber,
Coder: TopLevelEncoder>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Coder.Output == Downstream.Input,
Upstream.Output: Encodable,
Downstream.Failure == Error {
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
private let _encoder: Coder
var description: String { return "Encode" }
init(downstream: Downstream, encoder: Coder) {
self._encoder = encoder
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
let value = try _encoder.encode(input)
return downstream.receive(value)
} catch {
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
}
extension Publisher {
public func encode<Coder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder>
where Coder: TopLevelEncoder
{
return Publishers.Encode(upstream: self, encoder: encoder)
}
}
@@ -0,0 +1,167 @@
${template_header}
//
// Publishers.Encode.swift.gyb
//
//
// Created by Joseph Spadafora on 6/22/19.
//
%{
instantiations = ['Encode', 'Decode']
}%
extension Publisher {
/// Encodes the output from upstream using a specified `TopLevelEncoder`.
/// For example, use `JSONEncoder`.
public func encode<Coder: TopLevelEncoder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder> {
return .init(upstream: self, encoder: encoder)
}
/// Decodes the output from upstream using a specified `TopLevelDecoder`.
/// For example, use `JSONDecoder`.
public func decode<Item: Decodable, Coder: TopLevelDecoder>(
type: Item.Type,
decoder: Coder
) -> Publishers.Decode<Self, Item, Coder> where Output == Coder.Input {
return .init(upstream: self, decoder: decoder)
}
}
extension Publishers {
public struct Encode<Upstream: Publisher, Coder: TopLevelEncoder>: Publisher
where Upstream.Output: Encodable
{
public typealias Failure = Error
public typealias Output = Coder.Output
public let upstream: Upstream
private let _encode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self._encode = encoder.encode
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, encode: _encode))
}
}
public struct Decode<Upstream: Publisher, Output: Decodable, Coder: TopLevelDecoder>
: Publisher
where Upstream.Output == Coder.Input
{
public typealias Failure = Error
public let upstream: Upstream
private let _decode: (Upstream.Output) throws -> Output
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decode = { try decoder.decode(Output.self, from: $0) }
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, decode: _decode))
}
}
}
% for instantiation in instantiations:
extension Publishers.${instantiation} {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
// NOTE: This class has been audited for thread safety.
// Combine doesn't use any locking here.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let ${instantiation.lower()}: (Upstream.Output) throws -> Output
private var finished = false
private var subscription: Subscription?
fileprivate init(
downstream: Downstream,
${instantiation.lower()}: @escaping (Upstream.Output) throws -> Output
) {
self.downstream = downstream
self.${instantiation.lower()} = ${instantiation.lower()}
}
func receive(subscription: Subscription) {
if finished || self.subscription != nil {
subscription.cancel()
return
}
self.subscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
if finished { return .none }
do {
return try downstream.receive(${instantiation.lower()}(input))
} catch {
finished = true
subscription?.cancel()
subscription = nil
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
if finished { return }
finished = true
subscription = nil
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
subscription?.request(demand)
}
func cancel() {
guard let subscription = self.subscription, !finished else { return }
subscription.cancel()
self.subscription = nil
finished = true
}
var description: String { return "${instantiation}" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("finished", finished),
("upstreamSubscription", subscription as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
% end
@@ -1,44 +0,0 @@
//
// Publishers.Fail.swift
//
//
// Created by Sergej Jaskiewicz on 19.06.2019.
//
extension Publishers {
/// A publisher that immediately terminates with the specified error.
public struct Fail<Output, Failure: Error>: Publisher {
/// Creates a publisher that immediately terminates with the specified failure.
///
/// - Parameter error: The failure to send when terminating the publisher.
public init(error: Failure) {
self.error = error
}
/// Creates publisher with the given output type, that immediately terminates with
/// the specified failure.
///
/// Use this initializer to create a `Fail` publisher that can work with
/// subscribers or publishers that expect a given output type.
/// - Parameters:
/// - outputType: The output type exposed by this publisher.
/// - failure: The failure to send when terminating the publisher.
public init(outputType: Output.Type, failure: Failure) {
self.error = failure
}
/// The failure to send when terminating the publisher.
public let error: Failure
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(error))
}
}
}
extension Publishers.Fail: Equatable where Failure: Equatable {}
@@ -0,0 +1,188 @@
//
// Publishers.Filter.swift
//
//
// Created by Joseph Spadafora on 7/3/19.
//
extension Publisher {
/// Republishes all elements that match a provided closure.
///
/// - Parameter isIncluded: A closure that takes one element and returns
/// a Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func filter(
_ isIncluded: @escaping (Output) -> Bool
) -> Publishers.Filter<Self> {
return Publishers.Filter(upstream: self, isIncluded: isIncluded)
}
/// Republishes all elements that match a provided error-throwing closure.
///
/// If the `isIncluded` closure throws an error, the publisher fails with that error.
///
/// - Parameter isIncluded: A closure that takes one element and returns a
/// Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Self> {
return Publishers.TryFilter(upstream: self, isIncluded: isIncluded)
}
}
extension Publishers.Filter {
public func filter(
_ isIncluded: @escaping (Output) -> Bool
) -> Publishers.Filter<Upstream> {
return .init(upstream: upstream) { self.isIncluded($0) && isIncluded($0) }
}
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Upstream> {
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
}
}
extension Publishers.TryFilter {
public func filter(
_ isIncluded: @escaping (Output) -> Bool
) -> Publishers.TryFilter<Upstream> {
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
}
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Upstream> {
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
}
}
extension Publishers {
/// A publisher that republishes all elements that match a provided closure.
public struct Filter<Upstream: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Upstream.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that indicates whether to republish an element.
public let isIncluded: (Upstream.Output) -> Bool
public init(upstream: Upstream, isIncluded: @escaping (Output) -> Bool) {
self.upstream = upstream
self.isIncluded = isIncluded
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded))
}
}
/// A publisher that republishes all elements that match
/// a provided error-throwing closure.
public struct TryFilter<Upstream>: Publisher where Upstream: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Upstream.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A error-throwing closure that indicates whether to republish an element.
public let isIncluded: (Upstream.Output) throws -> Bool
public init(upstream: Upstream,
isIncluded: @escaping (Upstream.Output) throws -> Bool) {
self.upstream = upstream
self.isIncluded = isIncluded
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input,
Downstream.Failure == Failure
{
upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded))
}
}
}
extension Publishers.Filter {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) -> Bool>
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
// NOTE: This class has been audited for thread safety
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
return filter(newValue) ? .continue(newValue) : .continue(nil)
}
override var description: String { return "Filter" }
}
}
extension Publishers.TryFilter {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
{
// NOTE: This class has been audited for thread safety
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Upstream.Output?, Error> {
do {
return try filter(newValue) ? .continue(newValue) : .continue(nil)
} catch {
return .failure(error)
}
}
override var description: String { return "TryFilter" }
}
}
@@ -0,0 +1,210 @@
//
// Publishers.First.swift
//
//
// Created by Joseph Spadafora on 7/8/19.
//
extension Publisher {
/// Publishes the first element of a stream, then finishes.
///
/// If this publisher doesnt receive any elements, it finishes without publishing.
/// - Returns: A publisher that only publishes the first element of a stream.
public func first() -> Publishers.First<Self> {
return .init(upstream: self)
}
/// Publishes the first element of a stream to
/// satisfy a predicate closure, then finishes.
///
/// The publisher ignores all elements after the first.
/// If this publisher 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.
public func first(
where predicate: @escaping (Output) -> Bool
) -> Publishers.FirstWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes the first element of a stream to satisfy a
/// throwing predicate closure, then finishes.
///
/// The publisher ignores all elements after the first. If this publisher
/// 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.
public func tryFirst(
where predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryFirstWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that publishes the first element of a stream, then finishes.
public struct First<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream) {
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber))
}
}
/// A publisher that only publishes the first element of a
/// stream to satisfy a predicate closure.
public struct FirstWhere<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether to publish an element.
public let predicate: (Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
/// A publisher that only publishes the first element of a stream
/// to satisfy a throwing predicate closure.
public struct TryFirstWhere<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether to publish an element.
public let predicate: (Output) throws -> Bool
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
extension Publishers.First: Equatable where Upstream: Equatable {}
extension Publishers.First {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
Void>
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream) {
super.init(downstream: downstream, initial: nil, reduce: ())
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
result = newValue
return .finished
}
override var description: String { return "First" }
}
}
extension Publishers.FirstWhere {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream, Output, Output, Failure, (Output) -> Bool>
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream, predicate: @escaping (Output) -> Bool) {
super.init(downstream: downstream, initial: nil, reduce: predicate)
}
override func receive(
newValue: Output
) -> PartialCompletion<Void, Downstream.Failure> {
if reduce(newValue) {
result = newValue
return .finished
} else {
return .continue
}
}
override var description: String { return "TryFirst" }
}
}
extension Publishers.TryFirstWhere {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Output,
Output,
Upstream.Failure,
(Output) throws -> Bool>
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Output) throws -> Bool) {
super.init(downstream: downstream, initial: nil, reduce: predicate)
}
override func receive(
newValue: Output
) -> PartialCompletion<Void, Error> {
do {
if try reduce(newValue) {
result = newValue
return .finished
} else {
return .continue
}
} catch {
return .failure(error)
}
}
override var description: String { return "TryFirstWhere" }
}
}
@@ -0,0 +1,430 @@
//
// Publishers.FlatMap.swift
//
// Created by Eric Patey on 16.08.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Transforms all elements from an upstream publisher into a new or existing
/// publisher.
///
/// `flatMap` merges the output from all returned publishers into a single stream of
/// output.
///
/// - Parameters:
/// - maxPublishers: The maximum number of publishers produced by this method.
/// - transform: A closure that takes an element as a parameter and returns a
/// publisher that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher into
/// a publisher of that elements type.
public func flatMap<Result, Child: Publisher>(
maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Output) -> Child
) -> Publishers.FlatMap<Child, Self>
where Result == Child.Output, Failure == Child.Failure {
return .init(upstream: self,
maxPublishers: maxPublishers,
transform: transform)
}
}
extension Publishers {
public struct FlatMap<Child: Publisher, Upstream: Publisher>: Publisher
where Child.Failure == Upstream.Failure
{
public typealias Output = Child.Output
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let maxPublishers: Subscribers.Demand
public let transform: (Upstream.Output) -> Child
public init(upstream: Upstream, maxPublishers: Subscribers.Demand,
transform: @escaping (Upstream.Output) -> Child) {
self.upstream = upstream
self.maxPublishers = maxPublishers
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
let inner = Inner(downstream: subscriber,
maxPublishers: maxPublishers,
map: transform)
subscriber.receive(subscription: inner)
upstream.subscribe(inner)
}
}
}
extension Publishers.FlatMap {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Child.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private typealias SubscriptionIndex = Int
/// All requests to this subscription should be made with the `outerLock`
/// acquired.
private var outerSubscription: Subscription?
// Must be recursive lock. Probably a bug in Combine.
/// The lock for requesting from `outerSubscription`.
private let outerLock = UnfairLock.allocate()
/// The lock for modifying the state. All mutable state here should be
/// read and modified with this lock acquired.
/// The only exception is the `downstreamRecursive` field, which is guarded
/// by the `downstreamLock`.
private let lock = UnfairLock.allocate()
// Must be recursive lock. Probably a bug in Combine.
/// All the calls to the downstream subscriber should be made with this lock
/// acquired.
private let downstreamLock = UnfairLock.allocate()
private let downstream: Downstream
private var downstreamDemand = Subscribers.Demand.none
/// This variable is set to `true` whenever we call `downstream.receive(_:)`,
/// and then set back to `false`.
private var downstreamRecursive = false
private var innerRecursive = false
private var subscriptions = [SubscriptionIndex : Subscription]()
private var nextInnerIndex: SubscriptionIndex = 0
private var pendingSubscriptions = 0
private var buffer = [(SubscriptionIndex, Child.Output)]()
private let maxPublishers: Subscribers.Demand
private let map: (Input) -> Child
private var cancelledOrCompleted = false
private var outerFinished = false
init(downstream: Downstream,
maxPublishers: Subscribers.Demand,
map: @escaping (Upstream.Output) -> Child) {
self.downstream = downstream
self.maxPublishers = maxPublishers
self.map = map
}
deinit {
outerLock.deallocate()
lock.deallocate()
downstreamLock.deallocate()
}
// MARK: - Subscriber
fileprivate func receive(subscription: Subscription) {
lock.lock()
guard outerSubscription == nil, !cancelledOrCompleted else {
lock.unlock()
subscription.cancel()
return
}
outerSubscription = subscription
lock.unlock()
subscription.request(maxPublishers)
}
fileprivate func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
let cancelledOrCompleted = self.cancelledOrCompleted
lock.unlock()
if cancelledOrCompleted {
return .none
}
let child = map(input)
lock.lock()
let innerIndex = nextInnerIndex
nextInnerIndex += 1
pendingSubscriptions += 1
lock.unlock()
child.subscribe(Side(index: innerIndex, inner: self))
return .none
}
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
outerSubscription = nil
lock.lock()
outerFinished = true
switch completion {
case .finished:
releaseLockThenSendCompletionDownstreamIfNeeded(outerFinished: true)
return
case .failure:
let wasAlreadyCancelledOrCompleted = cancelledOrCompleted
cancelledOrCompleted = true
for (_, subscription) in subscriptions {
subscription.cancel()
}
subscriptions = [:]
lock.unlock()
if wasAlreadyCancelledOrCompleted {
return
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
}
// MARK: - Subscription
fileprivate func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
if downstreamRecursive {
// downstreamRecursive being true means that downstreamLock
// is already acquired.
downstreamDemand += demand
return
}
lock.lock()
if cancelledOrCompleted {
lock.unlock()
return
}
if demand == .unlimited {
downstreamDemand = .unlimited
let buffer = self.buffer
self.buffer = []
let subscriptions = self.subscriptions
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
for (_, childOutput) in buffer {
_ = downstream.receive(childOutput)
}
downstreamRecursive = false
downstreamLock.unlock()
for (_, subscription) in subscriptions {
subscription.request(.unlimited)
}
lock.lock()
} else {
downstreamDemand += demand
while !buffer.isEmpty && downstreamDemand > 0 {
// FIXME: This has quadratic complexity.
// This is what Combine does.
// Can we improve perfomance by using e. g. Deque instead of Array?
// Or array's cache locality makes this solution more efficient?
// Must benchmark before optimizing!
//
// https://www.cocoawithlove.com/blog/2016/09/22/deque.html
let (index, value) = buffer.removeFirst()
downstreamDemand -= 1
let subscription = subscriptions[index]
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
let additionalDemand = downstream.receive(value)
downstreamRecursive = false
downstreamLock.unlock()
if additionalDemand != .none {
lock.lock()
downstreamDemand += additionalDemand
lock.unlock()
}
if let subscription = subscription {
innerRecursive = true
subscription.request(.max(1))
innerRecursive = false
}
lock.lock()
}
}
releaseLockThenSendCompletionDownstreamIfNeeded(outerFinished: outerFinished)
}
fileprivate func cancel() {
lock.lock()
cancelledOrCompleted = true
let subscriptions = self.subscriptions
self.subscriptions = [:]
lock.unlock()
for (_, subscription) in subscriptions {
subscription.cancel()
}
// Combine doesn't acquire the lock here. Weird.
outerSubscription?.cancel()
outerSubscription = nil
}
// MARK: - Reflection
fileprivate var description: String { return "FlatMap" }
fileprivate var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
fileprivate var playgroundDescription: Any { return description }
// MARK: - Private
private func receiveInner(subscription: Subscription,
_ index: SubscriptionIndex) {
lock.lock()
pendingSubscriptions -= 1
subscriptions[index] = subscription
let demand = downstreamDemand == .unlimited
? Subscribers.Demand.unlimited
: .max(1)
lock.unlock()
subscription.request(demand)
}
private func receiveInner(_ input: Child.Output,
_ index: SubscriptionIndex) -> Subscribers.Demand {
lock.lock()
if downstreamDemand == .unlimited {
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
_ = downstream.receive(input)
downstreamRecursive = false
downstreamLock.unlock()
return .none
}
if downstreamDemand == .none || innerRecursive {
buffer.append((index, input))
lock.unlock()
return .none
}
downstreamDemand -= 1
lock.unlock()
downstreamLock.lock()
downstreamRecursive = true
let newDemand = downstream.receive(input)
downstreamRecursive = false
downstreamLock.unlock()
if newDemand > 0 {
lock.lock()
downstreamDemand += newDemand
lock.unlock()
}
return .max(1)
}
private func receiveInner(completion: Subscribers.Completion<Child.Failure>,
_ index: SubscriptionIndex) {
switch completion {
case .finished:
lock.lock()
subscriptions.removeValue(forKey: index)
let downstreamCompleted = releaseLockThenSendCompletionDownstreamIfNeeded(
outerFinished: outerFinished
)
if !downstreamCompleted {
requestOneMorePublisher()
}
case .failure:
lock.lock()
if cancelledOrCompleted {
lock.unlock()
return
}
cancelledOrCompleted = true
let subscriptions = self.subscriptions
self.subscriptions = [:]
lock.unlock()
for (i, subscription) in subscriptions where i != index {
subscription.cancel()
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
}
private func requestOneMorePublisher() {
if maxPublishers != .unlimited {
outerLock.lock()
outerSubscription?.request(.max(1))
outerLock.unlock()
}
}
/// - Precondition: `lock` is acquired
/// - Postcondition: `lock` is released
///
/// - Returns: `true` if a completion was sent downstream
@discardableResult
private func releaseLockThenSendCompletionDownstreamIfNeeded(
outerFinished: Bool
) -> Bool {
#if DEBUG
lock.assertOwner() // Sanity check
#endif
if !cancelledOrCompleted && outerFinished && buffer.isEmpty &&
subscriptions.count + pendingSubscriptions == 0 {
cancelledOrCompleted = true
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: .finished)
downstreamLock.unlock()
return true
}
lock.unlock()
return false
}
// MARK: - Side
private struct Side: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible {
private let index: SubscriptionIndex
private let inner: Inner
fileprivate let combineIdentifier = CombineIdentifier()
fileprivate init(index: SubscriptionIndex, inner: Inner) {
self.index = index
self.inner = inner
}
fileprivate func receive(subscription: Subscription) {
inner.receiveInner(subscription: subscription, index)
}
fileprivate func receive(_ input: Child.Output) -> Subscribers.Demand {
return inner.receiveInner(input, index)
}
fileprivate func receive(completion: Subscribers.Completion<Child.Failure>) {
inner.receiveInner(completion: completion, index)
}
fileprivate var description: String { return "FlatMap" }
fileprivate var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(
("parentSubscription", inner.combineIdentifier)
)
return Mirror(self, children: children)
}
fileprivate var playgroundDescription: Any { return description }
}
}
}
@@ -0,0 +1,197 @@
//
// Publishers.HandleEvents.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Performs the specified closures when publisher events occur.
///
/// - Parameters:
/// - receiveSubscription: A closure that executes when the publisher receives
/// the subscription from the upstream publisher. Defaults to `nil`.
/// - receiveOutput: A closure that executes when the publisher receives a value
/// from the upstream publisher. Defaults to `nil`.
/// - receiveCompletion: A closure that executes when the publisher receives
/// the completion from the upstream publisher. Defaults to `nil`.
/// - receiveCancel: A closure that executes when the downstream receiver cancels
/// publishing. Defaults to `nil`.
/// - receiveRequest: A closure that executes when the publisher receives a request
/// for more elements. Defaults to `nil`.
/// - Returns: A publisher that performs the specified closures when publisher events
/// occur.
public func handleEvents(
receiveSubscription: ((Subscription) -> Void)? = nil,
receiveOutput: ((Output) -> Void)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
receiveCancel: (() -> Void)? = nil,
receiveRequest: ((Subscribers.Demand) -> Void)? = nil
) -> Publishers.HandleEvents<Self> {
return .init(upstream: self,
receiveSubscription: receiveSubscription,
receiveOutput: receiveOutput,
receiveCompletion: receiveCompletion,
receiveCancel: receiveCancel,
receiveRequest: receiveRequest)
}
}
extension Publishers {
/// A publisher that performs the specified closures when publisher events occur.
public struct HandleEvents<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that executes when the publisher receives the subscription from
/// the upstream publisher.
public var receiveSubscription: ((Subscription) -> Void)?
/// A closure that executes when the publisher receives a value from the upstream
/// publisher.
public var receiveOutput: ((Upstream.Output) -> Void)?
/// A closure that executes when the publisher receives the completion from
/// the upstream publisher.
public var receiveCompletion:
((Subscribers.Completion<Upstream.Failure>) -> Void)?
/// A closure that executes when the downstream receiver cancels publishing.
public var receiveCancel: (() -> Void)?
/// A closure that executes when the publisher receives a request for more
/// elements.
public var receiveRequest: ((Subscribers.Demand) -> Void)?
public init(
upstream: Upstream,
receiveSubscription: ((Subscription) -> Void)? = nil,
receiveOutput: ((Output) -> Void)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
receiveCancel: (() -> Void)? = nil,
receiveRequest: ((Subscribers.Demand) -> Void)?
) {
self.upstream = upstream
self.receiveSubscription = receiveSubscription
self.receiveOutput = receiveOutput
self.receiveCompletion = receiveCompletion
self.receiveCancel = receiveCancel
self.receiveRequest = receiveRequest
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(self, downstream: subscriber)
subscriber.receive(subscription: inner)
upstream.subscribe(inner)
}
}
}
extension Publishers.HandleEvents {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private var status = SubscriptionStatus.awaitingSubscription
private var pendingDemand = Subscribers.Demand.none
private let lock = UnfairLock.allocate()
private var events: Publishers.HandleEvents<Upstream>?
private let downstream: Downstream
init(_ events: Publishers.HandleEvents<Upstream>, downstream: Downstream) {
self.events = events
self.downstream = downstream
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
events?.receiveSubscription?(subscription)
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
let pendingDemand = self.pendingDemand
self.pendingDemand = .none
lock.unlock()
if pendingDemand > 0 {
subscription.request(pendingDemand)
}
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
events?.receiveOutput?(input)
let newDemand = downstream.receive(input)
if newDemand > 0 {
events?.receiveRequest?(newDemand)
}
return newDemand
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
events?.receiveCompletion?(completion)
lock.lock()
events = nil
status = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
events?.receiveRequest?(demand)
lock.lock()
if case let .subscribed(subscription) = status {
lock.unlock()
subscription.request(demand)
return
}
pendingDemand += demand
lock.unlock()
}
func cancel() {
events?.receiveCancel?()
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
events = nil
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "HandleEvents" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,136 @@
//
// Publishers.IgnoreOutput.swift
//
// Created by Eric Patey on 16.08.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Ingores all upstream elements, but passes along a completion
/// state (finished or failed).
///
/// The output type of this publisher is `Never`.
/// - Returns: A publisher that ignores all upstream elements.
public func ignoreOutput() -> Publishers.IgnoreOutput<Self> {
return .init(upstream: self)
}
}
extension Publishers {
/// A publisher that ignores all upstream elements, but passes along a completion
/// state (finish or failed).
public struct IgnoreOutput<Upstream: Publisher>: Publisher {
public typealias Output = Never
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream) {
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Upstream.Failure, Downstream.Input == Never
{
upstream.subscribe(Inner<Downstream>(downstream: subscriber))
}
}
}
extension Publishers.IgnoreOutput {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Never, Downstream.Failure == Upstream.Failure
{
// NOTE: This class has been audited for thread safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream) {
self.downstream = downstream
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
subscription.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
// ignore and requests from downstream since we'll never send
// any values
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "IgnoreOutput" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("downstream", downstream),
("status", status)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.IgnoreOutput: Equatable where Upstream: Equatable {}
@@ -0,0 +1,203 @@
//
// Publishers.Last.swift
//
//
// Created by Joseph Spadafora on 7/9/19.
//
extension Publisher {
/// Only publishes the last element of a stream, after the stream finishes.
/// - Returns: A publisher that only publishes the last element of a stream.
public func last() -> Publishers.Last<Self> {
return .init(upstream: self)
}
/// Only publishes the last element of a stream that satisfies a predicate closure,
/// after the stream finishes.
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether to publish the element.
/// - Returns: A publisher that only publishes the last element satisfying
/// the given predicate.
public func last(
where predicate: @escaping (Output) -> Bool
) -> Publishers.LastWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Only publishes the last element of a stream that satisfies an error-throwing
/// predicate closure, after the stream finishes.
///
/// If the predicate closure throws, the publisher fails with the thrown error.
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether to publish the element.
/// - Returns: A publisher that only publishes the last element satisfying
/// the given predicate.
public func tryLast(
where predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryLastWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that only publishes the last element of a stream,
/// after the stream finishes.
public struct Last<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream) {
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber))
}
}
/// A publisher that only publishes the last element of a stream that satisfies
/// a predicate closure, once the stream finishes.
public struct LastWhere<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether to publish an element.
public let predicate: (Upstream.Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
/// A publisher that only publishes the last element of a stream that satisfies
/// an error-throwing predicate closure, once the stream finishes.
public struct TryLastWhere<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether to publish an element.
public let predicate: (Upstream.Output) throws -> Bool
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Error, Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
extension Publishers.Last: Equatable where Upstream: Equatable {}
extension Publishers.Last {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
Void>
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream) {
super.init(downstream: downstream, initial: nil, reduce: ())
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
result = newValue
return .continue
}
override var description: String { return "Last" }
}
}
extension Publishers.LastWhere {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) -> Bool>
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Upstream.Output) -> Bool) {
super.init(downstream: downstream, initial: nil, reduce: predicate)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
if reduce(newValue) {
result = newValue
}
return .continue
}
override var description: String { return "LastWhere" }
}
}
extension Publishers.TryLastWhere {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
fileprivate init(downstream: Downstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
super.init(downstream: downstream, initial: nil, reduce: predicate)
}
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
do {
if try reduce(newValue) {
result = newValue
}
return .continue
} catch {
return .failure(error)
}
}
override var description: String { return "TryLastWhere" }
}
}
@@ -0,0 +1,42 @@
//
// Publishers.MakeConnectable.swift
//
//
// Created by Sergej Jaskiewicz on 18/09/2019.
//
extension Publisher where Failure == Never {
/// Creates a connectable wrapper around the publisher.
///
/// - Returns: A `ConnectablePublisher` wrapping this publisher.
public func makeConnectable() -> Publishers.MakeConnectable<Self> {
return .init(upstream: self)
}
}
extension Publishers {
public struct MakeConnectable<Upstream: Publisher>: ConnectablePublisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
private let inner: Multicast<Upstream, PassthroughSubject<Output, Failure>>
public init(upstream: Upstream) {
inner = upstream.multicast(subject: .init())
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
inner.subscribe(subscriber)
}
public func connect() -> Cancellable {
return inner.connect()
}
}
}
@@ -5,6 +5,10 @@
// Created by Anton Nazarov on 25.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Transforms all elements from the upstream publisher with a provided closure.
@@ -30,7 +34,7 @@ extension Publisher {
/// - Returns: A publisher that uses the provided closure to map elements from
/// the upstream publisher to new elements that it then publishes.
public func tryMap<Result>(
_ transform: @escaping (Self.Output) throws -> Result
_ transform: @escaping (Output) throws -> Result
) -> Publishers.TryMap<Self, Result> {
return Publishers.TryMap(upstream: self, transform: transform)
}
@@ -39,7 +43,7 @@ extension Publisher {
extension Publishers {
/// A publisher that transforms all elements from the upstream publisher with
/// a provided closure.
public struct Map<Upstream: Publisher, Output> : Publisher {
public struct Map<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
@@ -48,6 +52,18 @@ extension Publishers {
/// The closure that transforms elements from the upstream publisher.
public let transform: (Upstream.Output) -> Output
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber, map: transform))
}
}
/// A publisher that transforms all elements from the upstream publisher
@@ -62,16 +78,16 @@ extension Publishers {
/// The error-throwing closure that transforms elements from
/// the upstream publisher.
public let transform: (Upstream.Output) throws -> Output
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) throws -> Output) {
self.upstream = upstream
self.transform = transform
}
}
}
extension Publishers.Map {
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
let inner = Inner(downstream: subscriber, transform: catching(transform))
upstream.receive(subscriber: inner)
}
public func map<Result>(
_ transform: @escaping (Output) -> Result
@@ -91,8 +107,7 @@ extension Publishers.TryMap {
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Error
{
let inner = Inner(downstream: subscriber, transform: catching(transform))
upstream.receive(subscriber: inner)
upstream.subscribe(Inner(downstream: subscriber, map: transform))
}
public func map<Result>(
@@ -108,84 +123,158 @@ extension Publishers.TryMap {
}
}
private class _Map<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
CustomStringConvertible,
Subscription
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Transform = (Input) -> Result<Downstream.Input, Downstream.Failure>
fileprivate var _transform: Transform?
var isCompleted: Bool {
return _transform == nil
}
init(downstream: Downstream, transform: @escaping Transform) {
_transform = transform
super.init(downstream: downstream)
}
var description: String { return "Map" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
switch _transform?(input) {
case .success(let output)?:
return downstream.receive(output)
case .failure(let error)?:
downstream.receive(completion: .failure(error))
_transform = nil
return .none
case nil:
return .none
}
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
override func cancel() {
_transform = nil
upstreamSubscription?.cancel()
}
}
extension Publishers.Map {
private final class Inner<Downstream: Subscriber>
: _Map<Upstream, Downstream>,
Subscriber
where Downstream.Failure == Upstream.Failure
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
{
func receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_transform = nil
downstream.receive(completion: completion)
}
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let map: (Input) -> Output
let combineIdentifier = CombineIdentifier()
fileprivate init(downstream: Downstream, map: @escaping (Input) -> Output) {
self.downstream = downstream
self.map = map
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(map(input))
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "Map" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryMap {
private final class Inner<Downstream: Subscriber>
: _Map<Upstream, Downstream>,
Subscriber
where Downstream.Failure == Error
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Error
{
func receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_transform = nil
downstream.receive(completion: completion.eraseError())
// NOTE: This class has been audited for thread-safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let map: (Input) throws -> Output
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
let combineIdentifier = CombineIdentifier()
fileprivate init(downstream: Downstream,
map: @escaping (Input) throws -> Output) {
self.downstream = downstream
self.map = map
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
return try downstream.receive(map(input))
} catch {
lock.lock()
let subscription: Subscription?
switch status {
case let .subscribed(upstreamSubscription):
subscription = upstreamSubscription
case .awaitingSubscription, .terminal:
subscription = nil
}
status = .terminal
lock.unlock()
subscription?.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "TryMap" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,109 @@
//
// Publishers.MapError.swift
//
//
// Created by Joseph Spadafora on 7/4/19.
//
extension Publishers {
/// A publisher that converts any failure from the
/// upstream publisher into a new error.
public struct MapError<Upstream: Publisher, Failure: Error>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Upstream.Output
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that converts the upstream failure into a new error.
public let transform: (Upstream.Failure) -> Failure
public init(upstream: Upstream, _ map: @escaping (Upstream.Failure) -> Failure) {
self.upstream = upstream
self.transform = map
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, map: transform))
}
}
}
extension Publisher {
/// Converts any failure from the upstream publisher into a new error.
///
/// Until the upstream publisher finishes normally or fails with an error,
/// the returned publisher republishes all the elements it receives.
///
/// - Parameter transform: A closure that takes the upstream failure as a
/// parameter and returns a new error for the publisher to terminate with.
/// - Returns: A publisher that replaces any upstream failure with a
/// new error produced by the `transform` closure.
public func mapError<NewFailure: Error>(
_ transform: @escaping (Failure) -> NewFailure
) -> Publishers.MapError<Self, NewFailure>
{
return Publishers.MapError(upstream: self, transform)
}
}
extension Publishers.MapError {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let map: (Upstream.Failure) -> Downstream.Failure
let combineIdentifier = CombineIdentifier()
var description: String { return "MapError" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
init(downstream: Downstream,
map: @escaping (Upstream.Failure) -> Downstream.Failure) {
self.downstream = downstream
self.map = map
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure(let error):
downstream.receive(completion: .failure(map(error)))
}
}
}
}
@@ -0,0 +1,175 @@
${template_header}
//
// Publishers.MapKeyPath.swift.gyb
//
//
// Created by Sergej Jaskiewicz on 03/10/2019.
//
%{
from gyb_opencombine_support import (
suffix_variadic,
list_with_suffix_variadic
)
instantiations = [(1, '', ''),
(2, 'two', 'second '),
(3, 'three', 'third ')]
def key_path_var(index, arity):
return suffix_variadic('keyPath', index, arity)
def make_publisher_name(arity):
return suffix_variadic('MapKeyPath', arity, arity)
def make_output_types(arity):
return list_with_suffix_variadic('Output', arity)
}%
extension Publisher {
% for arity, cardinal, _ in instantiations:
% result_types = list_with_suffix_variadic('Result', arity)
% cs_result_types = ', '.join(result_types)
%
% method_args = \
% ['_ {}: KeyPath<Output, {}>'.format(key_path_var(i, arity), result_types[i]) \
% for i in range(arity)]
% method_args_joined = ',\n '.join(method_args)
%
% init_args = ['{}: {}'.format(key_path_var(i, arity), key_path_var(i, arity)) \
% for i in range(arity)]
% init_args_joined = ',\n '.join(init_args)
%
% publisher_name = make_publisher_name(arity)
%
% doc_cardinal = 'a keyt path' if arity == 1 else cardinal + ' key paths'
/// Returns a publisher that publishes the values of ${doc_cardinal} as a tuple.
///
/// - Parameters:
% for i in range(arity):
% ordinal = 'another ' if i == 1 else 'a ' + instantiations[i][2]
/// - ${key_path_var(i, arity)}: The key path of ${ordinal}property on `Output`
% end
%
% doc_comment_suffix = 'value of the key path' \
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
/// - Returns: A publisher that publishes the ${doc_comment_suffix}.
public func map<${cs_result_types}>(
${method_args_joined}
) -> Publishers.${publisher_name}<Self, ${cs_result_types}> {
return .init(
upstream: self,
${init_args_joined}
)
}
% end
}
extension Publishers {
% for arity, cardinal, ordinal in instantiations:
%
% doc_comment_suffix = 'value of a key path' \
% if arity == 1 else 'values of {} key paths as a tuple'.format(cardinal)
%
% output_types = make_output_types(arity)
% cs_output_types = ', '.join(output_types)
%
% publisher_name = make_publisher_name(arity)
/// A publisher that publishes the ${doc_comment_suffix}.
public struct ${publisher_name}<Upstream: Publisher, ${cs_output_types}>: Publisher {
% if arity != 1:
public typealias Output = (${cs_output_types})
% end
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
% for i in range(arity):
% ordinal = instantiations[i][2]
/// The key path of a ${ordinal}property to publish.
public let ${key_path_var(i, arity)}: KeyPath<Upstream.Output, ${output_types[i]}>
% end
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber, parent: self))
}
}
% end
}
% for arity, _, _ in instantiations:
% output_types = make_output_types(arity)
% cs_output_types = ', '.join(output_types)
%
% publisher_name = make_publisher_name(arity)
extension Publishers.${publisher_name} {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
% for i in range(arity):
private let ${key_path_var(i, arity)}: KeyPath<Input, ${output_types[i]}>
% end
let combineIdentifier = CombineIdentifier()
fileprivate init(
downstream: Downstream,
parent: Publishers.${publisher_name}<Upstream, ${cs_output_types}>
) {
self.downstream = downstream
% for i in range(arity):
self.${key_path_var(i, arity)} = parent.${key_path_var(i, arity)}
% end
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
% output_components = \
% ['input[keyPath: {}]'.format(key_path_var(i, arity)) for i in range(arity)]
% output_components_joined = ',\n '.join(output_components)
let output = (
${output_components_joined}
)
return downstream.receive(output)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
% inner_description = 'ValueForKey' + ('' if arity == 1 else 's')
var description: String { return "${inner_description}" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
% for i in range(arity):
("${key_path_var(i, arity)}", ${key_path_var(i, arity)}),
% end
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
% end
@@ -0,0 +1,170 @@
//
// Publishers.MeasureInterval.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Measures and emits the time interval between events received from an upstream
/// publisher.
///
/// The output type of the returned scheduler is the time interval of the provided
/// scheduler.
///
/// - Parameters:
/// - scheduler: The scheduler on which to deliver elements.
/// - options: Options that customize the delivery of elements.
/// - Returns: A publisher that emits elements representing the time interval between
/// the elements it receives.
public func measureInterval<Context: Scheduler>(
using scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.MeasureInterval<Self, Context> {
return .init(upstream: self, scheduler: scheduler)
}
}
extension Publishers {
/// A publisher that measures and emits the time interval between events received from
/// an upstream publisher.
public struct MeasureInterval<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Context.SchedulerTimeType.Stride
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The scheduler on which to deliver elements.
public let scheduler: Context
public init(upstream: Upstream, scheduler: Context) {
self.upstream = upstream
self.scheduler = scheduler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Downstream.Input == Context.SchedulerTimeType.Stride
{
upstream.subscribe(Inner(self, downstream: subscriber))
}
}
}
extension Publishers.MeasureInterval {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Context.SchedulerTimeType.Stride,
Downstream.Failure == Upstream.Failure
{
// NOTE: This class has been audited for thread safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias MeasureInterval = Publishers.MeasureInterval<Upstream, Context>
private enum State {
case ready(MeasureInterval, Downstream)
case subscribed(MeasureInterval, Downstream, Subscription)
case terminal
}
private let lock = UnfairLock.allocate()
private var state: State
private var last: Context.SchedulerTimeType?
init(_ measureInterval: MeasureInterval, downstream: Downstream) {
state = .ready(measureInterval, downstream)
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case let .ready(measureInterval, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(measureInterval, downstream, subscription)
last = measureInterval.scheduler.now
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_: Input) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(measureInterval, downstream, subscription) = state,
let previousTime = last else {
lock.unlock()
return .none
}
let now = measureInterval.scheduler.now
last = now
lock.unlock()
let newDemand = downstream.receive(previousTime.distance(to: now))
if newDemand > 0 {
subscription.request(newDemand)
}
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case let .subscribed(_, downstream, _) = state else {
lock.unlock()
return
}
state = .terminal
last = nil
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
state = .terminal
last = nil
lock.unlock()
subscription.cancel()
}
var description: String { return "MeasureInterval" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
@@ -5,51 +5,21 @@
// Created by Sergej Jaskiewicz on 14.06.2019.
//
extension Publishers {
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
: ConnectablePublisher
where Upstream.Failure == SubjectType.Failure,
Upstream.Output == SubjectType.Output
{
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let createSubject: () -> SubjectType
private lazy var _subject: SubjectType = self.createSubject()
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
self.upstream = upstream
self.createSubject = createSubject
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where SubjectType.Failure == SubscriberType.Failure,
SubjectType.Output == SubscriberType.Input
{
_subject.subscribe(subscriber)
}
public func connect() -> Cancellable {
let subscriber = SubjectSubscriber(_subject)
upstream.subscribe(subscriber)
return AnyCancellable {
subscriber.parent = nil
}
}
}
}
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Applies a closure to create a subject that delivers elements to subscribers.
///
/// Use a multicast publisher when you have multiple downstream subscribers, but you
/// want upstream publishers to only process one `receive(_:)` call per event.
/// In contrast with `multicast(subject:)`, this method produces a publisher that
/// creates a separate Subject for each subscriber.
///
/// - Parameter createSubject: A closure to create a new Subject each time
/// a subscriber attaches to the multicast publisher.
public func multicast<SubjectType: Subject>(
_ createSubject: @escaping () -> SubjectType
) -> Publishers.Multicast<Self, SubjectType>
@@ -58,6 +28,14 @@ extension Publisher {
return Publishers.Multicast(upstream: self, createSubject: createSubject)
}
/// Provides a subject to deliver elements to multiple subscribers.
///
/// Use a multicast publisher when you have multiple downstream subscribers, but you
/// want upstream publishers to only process one `receive(_:)` call per event.
/// In contrast with `multicast(_:)`, this method produces a publisher shares
/// the provided Subject among all the downstream subscribers.
///
/// - Parameter subject: A subject to deliver elements to downstream subscribers.
public func multicast<SubjectType: Subject>(
subject: SubjectType
) -> Publishers.Multicast<Self, SubjectType>
@@ -66,3 +44,177 @@ extension Publisher {
return multicast { subject }
}
}
extension Publishers {
/// A publisher that uses a subject to deliver elements to multiple subscribers.
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
: ConnectablePublisher
where Upstream.Failure == SubjectType.Failure,
Upstream.Output == SubjectType.Output
{
// NOTE: This class has been audited for thread safety
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure to create a new Subject each time a subscriber attaches
/// to the multicast publisher.
public let createSubject: () -> SubjectType
private let lock = UnfairLock.allocate()
private var subject: SubjectType?
private var lazySubject: SubjectType {
lock.lock()
if let subject = subject {
lock.unlock()
return subject
}
let subject = createSubject()
self.subject = subject
lock.unlock()
return subject
}
/// Creates a multicast publisher that applies a closure to create a subject
/// that delivers elements to subscribers.
///
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
/// - Parameter createSubject: A closure to create a new Subject each time
/// a subscriber attaches to the multicast publisher.
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
self.upstream = upstream
self.createSubject = createSubject
}
deinit {
lock.deallocate()
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where SubjectType.Failure == Downstream.Failure,
SubjectType.Output == Downstream.Input
{
lazySubject.subscribe(Inner(parent: self, downstream: subscriber))
}
public func connect() -> Cancellable {
return upstream.subscribe(lazySubject)
}
}
}
extension Publishers.Multicast {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
// NOTE: This class has been audited for thread safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private enum State {
case ready(upstream: Upstream, downstream: Downstream)
case subscribed(upstream: Upstream,
downstream: Downstream,
subjectSubscription: Subscription)
case terminal
}
private let lock = UnfairLock.allocate()
private var state: State
fileprivate init(parent: Publishers.Multicast<Upstream, SubjectType>,
downstream: Downstream) {
state = .ready(upstream: parent.upstream, downstream: downstream)
}
deinit {
lock.deallocate()
}
fileprivate var description: String { return "Multicast" }
fileprivate var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
fileprivate var playgroundDescription: Any { return description }
func receive(subscription: Subscription) {
lock.lock()
guard case let .ready(upstream, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(upstream: upstream,
downstream: downstream,
subjectSubscription: subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(_, downstream, subjectSubscription) = state else {
lock.unlock()
return .none
}
lock.unlock()
let newDemand = downstream.receive(input)
if newDemand > 0 {
subjectSubscription.request(newDemand)
}
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard case let .subscribed(_, downstream, _) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(_, _, subjectSubscription) = state else {
lock.unlock()
return
}
lock.unlock()
subjectSubscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(_, _, subjectSubscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subjectSubscription.cancel()
}
}
}
@@ -1,387 +0,0 @@
//
// Publishers.Once.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Publishers {
/// A publisher that publishes an output to each subscriber exactly once then
/// finishes, or fails immediately without producing any elements.
///
/// If `result` is `.success`, then `Once` waits until it receives a request for
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
/// sends the failure immediately upon subscription.
///
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
/// value (unless it terminates with an error).
public struct Once<Output, Failure: Error>: Publisher {
/// The result to deliver to each subscriber.
public let result: Result<Output, Failure>
/// Creates a publisher that delivers the specified result.
///
/// If the result is `.success`, the `Once` publisher sends the specified output
/// to all subscribers and finishes normally. If the result is `.failure`, then
/// the publisher fails immediately with the specified error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ result: Result<Output, Failure>) {
self.result = result
}
/// Creates a publisher that sends the specified output to all subscribers and
/// finishes normally.
///
/// - Parameter output: The output to deliver to each subscriber.
public init(_ output: Output) {
self.init(.success(output))
}
/// Creates a publisher that immediately terminates upon subscription with
/// the given failure.
///
/// - Parameter failure: The failure to send when terminating.
public init(_ failure: Failure) {
self.init(.failure(failure))
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where SubscriberType.Input == Output, SubscriberType.Failure == Failure
{
switch result {
case .success(let value):
subscriber.receive(subscription: Inner(value: value,
downstream: subscriber))
case .failure(let failure):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(failure))
}
}
}
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
}
}
func cancel() {
_downstream = nil
}
var description: String { return "Once" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
}
}
extension Publishers.Once: Equatable where Output: Equatable, Failure: Equatable {}
extension Publishers.Once where Output: Equatable {
public func contains(_ output: Output) -> Publishers.Once<Bool, Failure> {
return Publishers.Once(result.map { $0 == output })
}
public func removeDuplicates() -> Publishers.Once<Output, Failure> {
return self
}
}
extension Publishers.Once where Output: Comparable {
public func min() -> Publishers.Once<Output, Failure> {
return self
}
public func max() -> Publishers.Once<Output, Failure> {
return self
}
}
extension Publishers.Once {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Publishers.Once<Bool, Failure> {
return Publishers.Once(result.map(predicate))
}
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(result.tryMap(predicate))
}
public func contains(
where predicate: (Output) -> Bool
) -> Publishers.Once<Bool, Failure> {
return Publishers.Once(result.map(predicate))
}
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(result.tryMap(predicate))
}
public func collect() -> Publishers.Once<[Output], Failure> {
return Publishers.Once(result.map { [$0] })
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func max(
by areInIncreasingOrder: (Output, Output
) -> Bool) -> Publishers.Once<Output, Failure> {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func count() -> Publishers.Once<Int, Failure> {
return Publishers.Once(result.map { _ in 1 })
}
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
precondition(count >= 0, "count must not be negative")
return Publishers.Optional((try? result.get()).flatMap { count == 0 ? $0 : nil })
}
public func drop(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? nil : $0 })
}
public func tryDrop(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? nil : $0 })
}
public func first() -> Publishers.Once<Output, Failure> {
return self
}
public func first(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
}
public func tryFirst(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
}
public func last() -> Publishers.Once<Output, Failure> {
return self
}
public func last(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
}
public func tryLast(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { isIncluded($0) ? $0 : nil })
}
public func tryFilter(
_ isIncluded: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try isIncluded($0) ? $0 : nil })
}
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
return Publishers.Empty()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Failure> {
return Publishers.Once(result.map(transform))
}
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(result.tryMap(transform))
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(result.map(transform))
}
public func tryCompactMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(result.tryMap(transform))
}
public func mapError<TransformedFailure: Error>(
_ transform: (Failure) -> TransformedFailure
) -> Publishers.Once<Output, TransformedFailure> {
return Publishers.Once(result.mapError(transform))
}
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
precondition(index >= 0, "index must not be negative")
return Publishers.Optional(result.map { index == 0 ? $0 : nil })
}
public func output<RangeExpr: RangeExpression>(
in range: RangeExpr
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
let range = range.relative(to: 0..<Int.max)
return Publishers.Optional(
result.map { range.lowerBound == 0 ? $0 : nil }
)
// The above implementation is used for compatibility.
//
// It actually probably should be just this:
// return Publishers.Optional(
// result.map { range.contains(0) ? $0 : nil }
// )
}
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
precondition(maxLength >= 0, "maxLength must not be negative")
// TODO: Seems broken in Apple's Combine (FB6168300)
return Publishers.Optional(
result.map { maxLength == 0 ? $0 : nil }
)
// The above implementation is used for compatibility.
//
// It actually should be the following:
// return Publishers.Optional(
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
// )
}
public func prefix(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
}
public func tryPrefix(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Error> {
return Publishers.Once(result.mapError { $0 })
}
public func replaceError(with output: Output) -> Publishers.Once<Output, Never> {
return Publishers.Once(.success(result.unwrapOr(output)))
}
public func replaceEmpty(with output: Output) -> Publishers.Once<Output, Failure> {
return self
}
public func retry(_ times: Int) -> Publishers.Once<Output, Failure> {
return self
}
public func retry() -> Publishers.Once<Output, Failure> {
return self
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Publishers.Once<Accumulator, Failure> {
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Publishers.Once<Accumulator, Error> {
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Failure> {
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
}
}
extension Publishers.Once where Failure == Never {
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Publishers.Once<Output, Failure> {
return Publishers.Once(result.success)
}
}
@@ -1,406 +0,0 @@
//
// Publishers.Optional.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Publishers {
/// A publisher that publishes an optional value to each subscriber exactly once, if
/// the optional has a value.
///
/// If `result` is `.success`, and the value is non-nil, then `Optional` waits until
/// receiving a request for at least 1 value before sending the output. If `result` is
/// `.failure`, then `Optional` sends the failure immediately upon subscription.
/// If `result` is `.success` and the value is nil, then `Optional` sends `.finished`
/// immediately upon subscription.
///
/// In contrast with `Just`, an `Optional` publisher can send an error.
/// In contrast with `Once`, an `Optional` publisher can send zero values and finish
/// normally, or send zero values and fail with an error.
public struct Optional<Output, Failure: Error>: Publisher {
// swiftlint:disable:previous syntactic_sugar
/// The result to deliver to each subscriber.
public let result: Result<Output?, Failure>
/// Creates a publisher to emit the optional value of a successful result, or fail
/// with an error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ result: Result<Output?, Failure>) {
self.result = result
}
public init(_ output: Output?) {
self.init(.success(output))
}
public init(_ failure: Failure) {
self.init(.failure(failure))
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
switch result {
case .success(let value?):
subscriber.receive(subscription: Inner(value: value,
downstream: subscriber))
case .success(nil):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .finished)
case .failure(let failure):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(failure))
}
}
}
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
}
}
func cancel() {
_downstream = nil
}
var description: String { return "Optional" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
}
}
extension Publishers.Optional: Equatable where Output: Equatable, Failure: Equatable {}
extension Publishers.Optional where Output: Equatable {
public func contains(_ output: Output) -> Publishers.Optional<Bool, Failure> {
return Publishers.Optional(result.map { $0 == output })
}
public func removeDuplicates() -> Publishers.Optional<Output, Failure> {
return self
}
}
extension Publishers.Optional where Output: Comparable {
public func min() -> Publishers.Optional<Output, Failure> {
return self
}
public func max() -> Publishers.Optional<Output, Failure> {
return self
}
}
extension Publishers.Optional {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Publishers.Optional<Bool, Failure> {
return Publishers.Optional(result.map { $0.map(predicate) })
}
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
}
public func collect() -> Publishers.Optional<[Output], Failure> {
return Publishers.Optional(result.map { $0.map { [$0] } })
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(result.map { $0.flatMap(transform) })
}
public func tryCompactMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(result.tryMap { try $0.flatMap(transform) })
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func max(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func contains(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Bool, Failure> {
return Publishers.Optional(result.map { $0.map(predicate) })
}
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
}
public func count() -> Publishers.Optional<Int, Failure> {
return Publishers.Optional(result.map { _ in 1 })
}
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
precondition(count >= 0, "count must not be negative")
return Publishers.Optional(try? result.get().flatMap { count == 0 ? $0 : nil })
}
public func drop(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? nil : $0 } })
}
public func tryDrop(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? nil : $0 } }
)
}
public func first() -> Publishers.Optional<Output, Failure> {
return self
}
public func first(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
}
public func tryFirst(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
)
}
public func last() -> Publishers.Optional<Output, Failure> {
return self
}
public func last(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
}
public func tryLast(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
)
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(
result.map { $0.flatMap { isIncluded($0) ? $0 : nil } }
)
}
public func tryFilter(
_ isIncluded: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try isIncluded($0) ? $0 : nil } }
)
}
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
return Publishers.Empty()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(result.map { $0.map(transform) })
}
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(result.tryMap { try $0.map(transform) })
}
public func mapError<TransformedFailure: Error>(
_ transform: (Failure) -> TransformedFailure
) -> Publishers.Optional<Output, TransformedFailure> {
return Publishers.Optional(result.mapError(transform))
}
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
precondition(index >= 0, "index must not be negative")
return Publishers.Optional(result.map { $0.flatMap { index == 0 ? $0 : nil } })
}
public func output<RangeExpr: RangeExpression>(
in range: RangeExpr
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
let range = range.relative(to: 0..<Int.max)
return Publishers.Optional(
result.map { $0.flatMap { range.lowerBound == 0 ? $0 : nil } }
)
// The above implementation is used for compatibility.
//
// It actually probably should be just this:
// return Publishers.Optional(
// result.map { $0.flatMap { range.contains(0) ? $0 : nil } }
// )
}
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
precondition(maxLength >= 0, "maxLength must not be negative")
// TODO: Seems broken in Apple's Combine (FB6168300)
return Publishers.Optional(
result.map { $0.flatMap { maxLength == 0 ? $0 : nil } }
)
// The above implementation is used for compatibility.
//
// It actually should be the following:
// return Publishers.Optional(
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
// )
}
public func prefix(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(
result.map { $0.flatMap { predicate($0) ? $0 : nil } }
)
}
public func tryPrefix(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
)
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Publishers.Optional<Accumulator, Failure> {
return Publishers.Optional(
result.map { $0.map { nextPartialResult(initialResult, $0) } }
)
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Publishers.Optional<Accumulator, Error> {
return Publishers.Optional(
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
)
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(
result.map { $0.map { nextPartialResult(initialResult, $0) } }
)
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
)
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.mapError { $0 })
}
public func replaceError(with output: Output) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(.success(result.unwrapOr(output)))
}
public func replaceEmpty(
with output: Output
) -> Publishers.Optional<Output, Failure> {
return self
}
public func retry(_ times: Int) -> Publishers.Optional<Output, Failure> {
return self
}
public func retry() -> Publishers.Optional<Output, Failure> {
return self
}
}
extension Publishers.Optional where Failure == Never {
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.success)
}
}
@@ -0,0 +1,209 @@
//
// Publishers.Output.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Republishes elements up to the specified maximum count.
///
/// - Parameter maxLength: The maximum number of elements to republish.
/// - Returns: A publisher that publishes up to the specified number of elements
/// before completing.
public func prefix(_ maxLength: Int) -> Publishers.Output<Self> {
return output(in: ..<maxLength)
}
}
extension Publisher {
/// Publishes a specific element, indicated by its index in the sequence of published
/// elements.
///
/// If the publisher completes normally or with an error before publishing
/// the specified element, then the publisher doesnt produce any elements.
///
/// - Parameter index: The index that indicates the element to publish.
/// - Returns: A publisher that publishes a specific indexed element.
public func output(at index: Int) -> Publishers.Output<Self> {
return output(in: index...index)
}
/// Publishes elements specified by their range in the sequence of published elements.
///
/// After all elements are published, the publisher finishes normally.
/// If the publisher completes normally or with an error before producing all
/// the elements in the range, it doesnt publish the remaining elements.
///
/// - Parameter range: A range that indicates which elements to publish.
/// - Returns: A publisher that publishes elements specified by a range.
public func output<Range: RangeExpression>(in range: Range) -> Publishers.Output<Self>
where Range.Bound == Int
{
return .init(upstream: self, range: range.relative(to: 0 ..< .max))
}
}
extension Publishers {
/// A publisher that publishes elements specified by a range in the sequence of
/// published elements.
public struct Output<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// The range of elements to publish.
public let range: CountableRange<Int>
/// Creates a publisher that publishes elements specified by a range.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - range: The range of elements to publish.
public init(upstream: Upstream, range: CountableRange<Int>) {
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
precondition(range.upperBound >= 0, "upperBound must not be negative")
self.upstream = upstream
self.range = range
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, range: range))
}
}
}
extension Publishers.Output: Equatable where Upstream: Equatable {}
extension Publishers.Output {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
// NOTE: This class has been audited for thread safety
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private var status = SubscriptionStatus.awaitingSubscription
private var remainingUntilStart: Int
private var remainingCount: Int
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream, range: CountableRange<Int>) {
self.downstream = downstream
self.remainingUntilStart = range.lowerBound
self.remainingCount = range.count
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
if remainingUntilStart > 0 {
remainingUntilStart -= 1
return .max(1)
}
let newDemand: Subscribers.Demand
if remainingCount > 0 {
remainingCount -= 1
newDemand = downstream.receive(input)
} else {
newDemand = .none
cancelUpstreamAndFinish()
}
return newDemand
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "Output" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
// MARK: - Private
private func cancelUpstreamAndFinish() {
assert(remainingCount == 0)
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
downstream.receive(completion: .finished)
}
}
}
@@ -0,0 +1,147 @@
//
// Publishers.PrefixWhile.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
extension Publisher {
/// Republishes elements while a predicate closure indicates publishing should
/// continue.
///
/// The publisher finishes when the closure returns `false`.
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether publishing should continue.
/// - Returns: A publisher that passes through elements until the predicate indicates
/// publishing should finish.
public func prefix(
while predicate: @escaping (Output) -> Bool
) -> Publishers.PrefixWhile<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Republishes elements while a error-throwing predicate closure indicates publishing
/// should continue.
///
/// The publisher finishes when the closure returns `false`. If the closure throws,
/// the publisher fails with the thrown error.
///
/// - Parameter predicate: A closure that takes an element as its parameter and
/// returns a Boolean value indicating whether publishing should continue.
/// - Returns: A publisher that passes through elements until the predicate throws or
/// indicates publishing should finish.
public func tryPrefix(
while predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryPrefixWhile<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that republishes elements while a predicate closure indicates
/// publishing should continue.
public struct PrefixWhile<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether whether publishing should continue.
public let predicate: (Upstream.Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
}
}
/// A publisher that republishes elements while an error-throwing predicate closure
/// indicates publishing should continue.
public struct TryPrefixWhile<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether publishing should continue.
public let predicate: (Upstream.Output) throws -> Bool
public init(upstream: Upstream,
predicate: @escaping (Upstream.Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
}
}
}
extension Publishers.PrefixWhile {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
override func receive(
newValue: Input
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
return filter(newValue) ? .continue(newValue) : .finished
}
override var description: String { return "PrefixWhile" }
}
}
extension Publishers.TryPrefixWhile {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Upstream.Output) throws -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
override func receive(
newValue: Input
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
do {
return try filter(newValue) ? .continue(newValue) : .finished
} catch {
return .failure(error)
}
}
override var description: String { return "TryPrefixWhile" }
}
}
@@ -5,6 +5,23 @@
// Created by Sergej Jaskiewicz on 16.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Prints log messages for all publishing events.
///
/// - Parameter prefix: A string with which to prefix all log messages. Defaults to
/// an empty string.
/// - Returns: A publisher that prints log messages for all publishing events.
public func print(_ prefix: String = "",
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
return .init(upstream: self, prefix: prefix, to: stream)
}
}
extension Publishers {
/// A publisher that prints log messages for all publishing events, optionally
@@ -43,121 +60,131 @@ extension Publishers {
self.stream = stream
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, prefix: prefix, stream: stream)
upstream.receive(subscriber: inner)
upstream.subscribe(inner)
}
}
}
extension Publisher {
extension Publishers.Print {
private final class Inner<Downstream: Subscriber>: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Downstream.Input
typealias Failure = Downstream.Failure
/// Prints log messages for all publishing events.
///
/// - Parameter prefix: A string with which to prefix all log messages. Defaults to
/// an empty string.
/// - Returns: A publisher that prints log messages for all publishing events.
public func print(_ prefix: String = "",
to stream: TextOutputStream? = nil) -> Publishers.Print<Self> {
return Publishers.Print(upstream: self, prefix: prefix, to: stream)
}
}
/// A concrete type wrapper around an abstract stream.
private struct PrintTarget: TextOutputStream {
private final class Inner<Downstream: Subscriber>: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable
{
typealias Input = Downstream.Input
typealias Failure = Downstream.Failure
var stream: TextOutputStream
private var _downstream: Downstream
private let _prefix: String
private var _stream: TextOutputStream
private var _upstreamSubscription: Subscription?
private let _printerLock = Lock(recursive: false)
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
_downstream = downstream
_prefix = prefix
_stream = stream ?? StdoutStream()
}
func receive(subscription: Subscription) {
_log("receive subscription", value: subscription)
_upstreamSubscription = subscription
_downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
_log("receive value", value: input)
let demand = _downstream.receive(input)
_logDemand(demand, synchronous: true)
return demand
}
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
_log("receive finished")
case .failure(let error):
_log("receive error", value: error)
}
_downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
_logDemand(demand, synchronous: false)
_upstreamSubscription?.request(demand)
}
func cancel() {
_log("receive cancel")
_upstreamSubscription?.cancel()
_upstreamSubscription = nil
}
var description: String { return "Print" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
private func _log(_ description: String,
value: Any? = nil,
additionalInfo: String = "") {
_printerLock.do {
if !_prefix.isEmpty {
_stream.write(_prefix)
_stream.write(": ")
mutating func write(_ string: String) {
stream.write(string)
}
_stream.write(description)
if let value = value {
_stream.write(": (")
_stream.write(String(describing: value))
_stream.write(")")
}
if !additionalInfo.isEmpty {
_stream.write(" (")
_stream.write(additionalInfo)
_stream.write(")")
}
_stream.write("\n")
}
}
private func _logDemand(_ demand: Subscribers.Demand, synchronous: Bool) {
let synchronouslyStr = synchronous ? "synchronous" : ""
if let max = demand.max {
_log("request max", value: max, additionalInfo: synchronouslyStr)
} else {
_log("request unlimited", additionalInfo: synchronouslyStr)
private var downstream: Downstream
private let prefix: String
private var stream: PrintTarget?
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
self.downstream = downstream
self.prefix = prefix.isEmpty ? "" : "\(prefix): "
self.stream = stream.map(PrintTarget.init)
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
log("\(prefix)receive subscription: (\(subscription))")
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
log("\(prefix)receive value: (\(input))")
let demand = downstream.receive(input)
if let max = demand.max {
log("\(prefix)request max: (\(max)) (synchronous)")
} else {
log("\(prefix)request unlimited (synchronous)")
}
return demand
}
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
log("\(prefix)receive finished")
case .failure(let error):
log("\(prefix)receive error: (\(error))")
}
lock.lock()
status = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
if let max = demand.max {
log("\(prefix)request max: (\(max))")
} else {
log("\(prefix)request unlimited")
}
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
log("\(prefix)receive cancel")
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "Print" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
// MARK: - Private
private func log(_ text: String) {
if var stream = stream {
Swift.print(text, to: &stream)
} else {
Swift.print(text)
}
}
}
}
private struct StdoutStream: TextOutputStream {
mutating func write(_ string: String) {
print(string, terminator: "")
}
}
@@ -0,0 +1,217 @@
//
// Publishers.ReceiveOn.swift
//
//
// Created by Sergej Jaskiewicz on 02.12.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Specifies the scheduler on which to receive elements from the publisher.
///
/// You use the `receive(on:options:)` operator to receive results on a specific
/// scheduler, such as performing UI work on the main run loop.
/// In contrast with `subscribe(on:options:)`, which affects upstream messages,
/// `receive(on:options:)` changes the execution context of downstream messages.
/// In the following example, requests to `jsonPublisher` are performed on
/// `backgroundQueue`, but elements received from it are performed on `RunLoop.main`.
///
/// // Some publisher.
/// let jsonPublisher = MyJSONLoaderPublisher()
///
/// // Some subscriber that updates the UI.
/// let labelUpdater = MyLabelUpdateSubscriber()
///
/// jsonPublisher
/// .subscribe(on: backgroundQueue)
/// .receiveOn(on: RunLoop.main)
/// .subscribe(labelUpdater)
///
/// - Parameters:
/// - scheduler: The scheduler the publisher is to use for element delivery.
/// - options: Scheduler options that customize the element delivery.
/// - Returns: A publisher that delivers elements using the specified scheduler.
public func receive<Context: Scheduler>(
on scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.ReceiveOn<Self, Context> {
return .init(upstream: self, scheduler: scheduler, options: options)
}
}
extension Publishers {
/// A publisher that delivers elements to its downstream subscriber on a specific
/// scheduler.
public struct ReceiveOn<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The scheduler the publisher is to use for element delivery.
public let scheduler: Context
/// Scheduler options that customize the delivery of elements.
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.upstream = upstream
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(self, downstream: subscriber))
}
}
}
extension Publishers.ReceiveOn {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias ReceiveOn = Publishers.ReceiveOn<Upstream, Context>
private enum State {
case ready(ReceiveOn, Downstream)
case subscribed(ReceiveOn, Downstream, Subscription)
case terminal
}
private let lock = UnfairLock.allocate()
private var state: State
private let downstreamLock = UnfairLock.allocate()
init(_ receiveOn: ReceiveOn, downstream: Downstream) {
state = .ready(receiveOn, downstream)
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case let .ready(receiveOn, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(receiveOn, downstream, subscription)
lock.unlock()
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
self?.scheduledReceive(subscription: subscription)
}
}
private func scheduledReceive(subscription: Subscription) {
lock.lock()
guard case let .subscribed(_, downstream, _) = state else {
lock.unlock()
return
}
lock.unlock()
downstreamLock.lock()
downstream.receive(subscription: self)
downstreamLock.unlock()
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(receiveOn, downstream, _) = state else {
lock.unlock()
return .none
}
lock.unlock()
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
self?.scheduledReceive(input, downstream: downstream)
}
return .none
}
private func scheduledReceive(_ input: Upstream.Output, downstream: Downstream) {
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
guard newDemand > 0 else {
return
}
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(newDemand)
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case let .subscribed(receiveOn, downstream, _) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
receiveOn.scheduler.schedule(options: receiveOn.options) { [weak self] in
self?.scheduledReceive(completion: completion, downstream: downstream)
}
}
private func scheduledReceive(completion: Subscribers.Completion<Failure>,
downstream: Downstream) {
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "ReceiveOn" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,168 @@
//
// Publishers.Reduce.swift
//
//
// Created by Sergej Jaskiewicz on 09.10.2019.
//
extension Publisher {
/// Applies a closure that accumulates each element of a stream and publishes
/// a final result upon completion.
///
/// - Parameters:
/// - initialResult: The value the closure receives the first time it is called.
/// - nextPartialResult: A closure that takes the previously-accumulated value and
/// the next element from the upstream publisher to produce a new value.
/// - Returns: A publisher that applies the closure to all received elements and
/// produces an accumulated value when the upstream publisher finishes.
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: @escaping (Accumulator, Output) -> Accumulator
) -> Publishers.Reduce<Self, Accumulator> {
return .init(upstream: self,
initial: initialResult,
nextPartialResult: nextPartialResult)
}
/// Applies an error-throwing closure that accumulates each element of a stream and
/// publishes a final result upon completion.
///
/// If the closure throws an error, the publisher fails, passing the error
/// to its subscriber.
///
/// - Parameters:
/// - initialResult: The value the closure receives the first time it is called.
/// - nextPartialResult: An error-throwing closure that takes
/// the previously-accumulated value and the next element from the upstream
/// publisher to produce a new value.
/// - Returns: A publisher that applies the closure to all received elements and
/// produces an accumulated value when the upstream publisher finishes.
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: @escaping (Accumulator, Output) throws -> Accumulator
) -> Publishers.TryReduce<Self, Accumulator> {
return .init(upstream: self,
initial: initialResult,
nextPartialResult: nextPartialResult)
}
}
extension Publishers {
/// A publisher that applies a closure to all received elements and produces
/// an accumulated value when the upstream publisher finishes.
public struct Reduce<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
public let upstream: Upstream
/// The initial value provided on the first invocation of the closure.
public let initial: Output
/// A closure that takes the previously-accumulated value and the next element
/// from the upstream publisher to produce a new value.
public let nextPartialResult: (Output, Upstream.Output) -> Output
public init(upstream: Upstream,
initial: Output,
nextPartialResult: @escaping (Output, Upstream.Output) -> Output) {
self.upstream = upstream
self.initial = initial
self.nextPartialResult = nextPartialResult
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
let inner = Inner(downstream: subscriber,
initial: initial,
reduce: nextPartialResult)
upstream.subscribe(inner)
}
}
/// A publisher that applies an error-throwing closure to all received elements and
/// produces an accumulated value when the upstream publisher finishes.
public struct TryReduce<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The initial value provided on the first invocation of the closure.
public let initial: Output
/// An error-throwing closure that takes the previously-accumulated value and
/// the next element from the upstream to produce a new value.
///
/// If this closure throws an error, the publisher fails and passes the error
/// to its subscriber.
public let nextPartialResult: (Output, Upstream.Output) throws -> Output
public init(
upstream: Upstream,
initial: Output,
nextPartialResult: @escaping (Output, Upstream.Output) throws -> Output
) {
self.upstream = upstream
self.initial = initial
self.nextPartialResult = nextPartialResult
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Error
{
let inner = Inner(downstream: subscriber,
initial: initial,
reduce: nextPartialResult)
upstream.subscribe(inner)
}
}
}
extension Publishers.Reduce {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Output,
Upstream.Failure,
(Output, Upstream.Output) -> Output>
where Downstream.Input == Output, Upstream.Failure == Downstream.Failure
{
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
result = reduce(result!, newValue)
return .continue
}
override var description: String { return "Reduce" }
}
}
extension Publishers.TryReduce {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream,
Upstream.Output,
Output,
Upstream.Failure,
(Output, Upstream.Output) throws -> Output>
where Downstream.Input == Output, Downstream.Failure == Error
{
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
do {
result = try reduce(result!, newValue)
return .continue
} catch {
return .failure(error)
}
}
override var description: String { return "TryReduce" }
}
}
@@ -0,0 +1,196 @@
//
// Publishers.RemoveDuplicates.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
extension Publisher where Output: Equatable {
/// Publishes only elements that dont match the previous element.
///
/// - Returns: A publisher that consumes rather than publishes duplicate elements.
public func removeDuplicates() -> Publishers.RemoveDuplicates<Self> {
return removeDuplicates(by: ==)
}
}
extension Publisher {
/// Publishes only elements that dont match the previous element, as evaluated by
/// a provided closure.
///
/// - Parameter predicate: A closure to evaluate whether two elements are equivalent,
/// for purposes of filtering. Return `true` from this closure to indicate that
/// the second element is a duplicate of the first.
public func removeDuplicates(
by predicate: @escaping (Output, Output) -> Bool
) -> Publishers.RemoveDuplicates<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes only elements that dont match the previous element, as evaluated by
/// a provided error-throwing closure.
///
/// - Parameter predicate: A closure to evaluate whether two elements are equivalent,
/// for purposes of filtering. Return `true` from this closure to indicate that
/// the second element is a duplicate of the first. If this closure throws an error,
/// the publisher terminates with the thrown error.
public func tryRemoveDuplicates(
by predicate: @escaping (Output, Output) throws -> Bool
) -> Publishers.TryRemoveDuplicates<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that publishes only elements that dont match the previous element.
public struct RemoveDuplicates<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure to evaluate whether two elements are equivalent,
/// for purposes of filtering.
public let predicate: (Output, Output) -> Bool
/// Creates a publisher that publishes only elements that dont match the previou
/// element, as evaluated by a provided closure.
///
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
/// - Parameter predicate: A closure to evaluate whether two elements are
/// equivalent, for purposes of filtering. Return `true` from this closure
/// to indicate that the second element is a duplicate of the first.
public init(upstream: Upstream, predicate: @escaping (Output, Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
}
}
/// A publisher that publishes only elements that dont match the previous element,
/// as evaluated by a provided error-throwing closure.
public struct TryRemoveDuplicates<Upstream: Publisher>: Publisher{
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// An error-throwing closure to evaluate whether two elements are equivalent,
/// for purposes of filtering.
public let predicate: (Output, Output) throws -> Bool
/// Creates a publisher that publishes only elements that dont match the previous
/// element, as evaluated by a provided error-throwing closure.
///
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
/// - Parameter predicate: An error-throwing closure to evaluate whether two
/// elements are equivalent, for purposes of filtering. Return `true` from this
/// closure to indicate that the second element is a duplicate of the first.
/// If this closure throws an error, the publisher terminates
/// with the thrown error.
public init(upstream: Upstream,
predicate: @escaping (Output, Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
upstream.subscribe(Inner(downstream: subscriber, filter: predicate))
}
}
}
extension Publishers.RemoveDuplicates {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Output, Output) -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
// NOTE: This class has been audited for thread-safety
private var last: Upstream.Output?
override func receive(
newValue: Input
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
let last = self.last
self.last = newValue
return last.map {
filter($0, newValue) ? .continue(nil) : .continue(newValue)
} ?? .continue(newValue)
}
override var description: String { return "RemoveDuplicates" }
override var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("last", last as Any)
]
return Mirror(self, children: children)
}
}
}
extension Publishers.TryRemoveDuplicates {
private final class Inner<Downstream: Subscriber>
: FilterProducer<Downstream,
Upstream.Output,
Upstream.Output,
Upstream.Failure,
(Output, Output) throws -> Bool>
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
{
// NOTE: This class has been audited for thread-safety
private var last: Upstream.Output?
override func receive(
newValue: Input
) -> PartialCompletion<Upstream.Output?, Downstream.Failure> {
let last = self.last
self.last = newValue
return last.map {
do {
return try filter($0, newValue)
? .continue(nil)
: .continue(newValue)
} catch {
return .failure(error)
}
} ?? .continue(newValue)
}
override var description: String { return "TryRemoveDuplicates" }
override var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("last", last as Any)
]
return Mirror(self, children: children)
}
}
}
@@ -0,0 +1,192 @@
//
// Publishers.ReplaceError.swift
// OpenCombine
//
// Created by Bogdan Vlad on 8/29/19.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Replaces any errors in the stream with the provided element.
///
/// If the upstream publisher fails with an error, this publisher emits the provided
/// element, then finishes normally.
/// - Parameter output: An element to emit when the upstream publisher fails.
/// - Returns: A publisher that replaces an error from the upstream publisher with
/// the provided output element.
public func replaceError(with output: Output) -> Publishers.ReplaceError<Self> {
return .init(upstream: self, output: output)
}
}
extension Publishers {
/// A publisher that replaces any errors in the stream with a provided element.
public struct ReplaceError<Upstream: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Upstream.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
/// The element with which to replace errors from the upstream publisher.
public let output: Upstream.Output
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream,
output: Output) {
self.upstream = upstream
self.output = output
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, output: output)
upstream.subscribe(inner)
subscriber.receive(subscription: inner)
}
}
}
extension Publishers.ReplaceError: Equatable
where Upstream: Equatable, Upstream.Output: Equatable
{}
extension Publishers.ReplaceError {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input
{
// NOTE: this class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let output: Upstream.Output
private let downstream: Downstream
private var status = SubscriptionStatus.awaitingSubscription
private var terminated = false
private var pendingDemand = Subscribers.Demand.none
private var lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream, output: Upstream.Output) {
self.downstream = downstream
self.output = output
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
let pendingDemand = self.pendingDemand
lock.unlock()
if pendingDemand > 0 {
subscription.request(pendingDemand)
}
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return .none
}
pendingDemand -= 1
lock.unlock()
let demand = downstream.receive(input)
guard demand > 0 else {
return .none
}
lock.lock()
pendingDemand += demand
lock.unlock()
return demand
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure:
lock.lock()
// If there was no demand from downstream,
// ReplaceError does not forward the value that
// replaces the error until it is requested.
guard pendingDemand > 0 else {
terminated = true
lock.unlock()
return
}
lock.unlock()
_ = downstream.receive(output)
downstream.receive(completion: .finished)
}
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
if terminated {
status = .terminal
lock.unlock()
_ = downstream.receive(output)
downstream.receive(completion: .finished)
return
}
pendingDemand += demand
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "ReplaceError" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,22 @@
//
// Publishers.ReplaceNil.swift
//
//
// Created by Joseph Spadafora on 7/4/19.
//
extension Publisher {
/// Replaces nil elements in the stream with the proviced element.
///
/// - Parameter output: The element to use when replacing `nil`.
/// - Returns: A publisher that replaces `nil` elements from
/// the upstream publisher with the provided element.
public func replaceNil<ElementOfResult>(
with output: ElementOfResult
) -> Publishers.Map<Self, ElementOfResult>
where Output == ElementOfResult?
{
return Publishers.Map(upstream: self) { $0 ?? output }
}
}
@@ -0,0 +1,300 @@
//
// Publishers.Scan.swift
//
// Created by Eric Patey on 26.08.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Transforms elements from the upstream publisher by providing the current element
/// to a closure along with the last value returned by the closure.
///
/// let pub = (0...5)
/// .publisher
/// .scan(0, { return $0 + $1 })
/// .sink(receiveValue: { print ("\($0)", terminator: " ") })
/// // Prints "0 1 3 6 10 15 ".
///
///
/// - Parameters:
/// - initialResult: The previous result returned by the `nextPartialResult`
/// closure.
/// - nextPartialResult: A closure that takes as its arguments the previous value
/// returned by the closure and the next element emitted from the upstream
/// publisher.
/// - Returns: A publisher that transforms elements by applying a closure that
/// receives its previous return value and the next element from the upstream
/// publisher.
public func scan<Result>(
_ initialResult: Result,
_ nextPartialResult: @escaping (Result, Output) -> Result
) -> Publishers.Scan<Self, Result> {
return .init(upstream: self,
initialResult: initialResult,
nextPartialResult: nextPartialResult)
}
/// Transforms elements from the upstream publisher by providing the current element
/// to an error-throwing closure along with the last value returned by the closure.
///
/// If the closure throws an error, the publisher fails with the error.
/// - Parameters:
/// - initialResult: The previous result returned by the `nextPartialResult`
/// closure.
/// - nextPartialResult: An error-throwing closure that takes as its arguments the
/// previous value returned by the closure and the next element emitted from the
/// upstream publisher.
/// - Returns: A publisher that transforms elements by applying a closure that
/// receives its previous return value and the next element from the upstream
/// publisher.
public func tryScan<Result>(
_ initialResult: Result,
_ nextPartialResult: @escaping (Result, Output) throws -> Result
) -> Publishers.TryScan<Self, Result> {
return .init(upstream: self,
initialResult: initialResult,
nextPartialResult: nextPartialResult)
}
}
extension Publishers {
public struct Scan<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let initialResult: Output
public let nextPartialResult: (Output, Upstream.Output) -> Output
public init(upstream: Upstream,
initialResult: Output,
nextPartialResult: @escaping (Output, Upstream.Output) -> Output) {
self.upstream = upstream
self.initialResult = initialResult
self.nextPartialResult = nextPartialResult
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber,
initialResult: initialResult,
nextPartialResult: nextPartialResult))
}
}
public struct TryScan<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Error
public let upstream: Upstream
public let initialResult: Output
public let nextPartialResult: (Output, Upstream.Output) throws -> Output
public init(
upstream: Upstream,
initialResult: Output,
nextPartialResult: @escaping (Output, Upstream.Output) throws -> Output
) {
self.upstream = upstream
self.initialResult = initialResult
self.nextPartialResult = nextPartialResult
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Error
{
upstream.subscribe(Inner(downstream: subscriber,
initialResult: initialResult,
nextPartialResult: nextPartialResult))
}
}
}
extension Publishers.Scan {
private final class Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Failure == Downstream.Failure
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let nextPartialResult: (Downstream.Input, Input) -> Downstream.Input
private var result: Downstream.Input
fileprivate init(
downstream: Downstream,
initialResult: Downstream.Input,
nextPartialResult: @escaping (Downstream.Input, Input) -> Downstream.Input
)
{
self.downstream = downstream
self.result = initialResult
self.nextPartialResult = nextPartialResult
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
result = nextPartialResult(result, input)
return downstream.receive(result)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "Scan" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("result", result)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryScan {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Failure == Error
{
// NOTE: this class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let nextPartialResult:
(Downstream.Input, Input) throws -> Downstream.Input
private var result: Downstream.Input
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
private var finished = false
fileprivate init(
downstream: Downstream,
initialResult: Downstream.Input,
nextPartialResult:
@escaping (Downstream.Input, Input) throws -> Downstream.Input
) {
self.downstream = downstream
self.nextPartialResult = nextPartialResult
self.result = initialResult
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
result = try nextPartialResult(result, input)
return downstream.receive(result)
} catch {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return .none
}
status = .terminal
lock.unlock()
subscription.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
// Combine doesn't use locking in this method!
guard case .subscribed = status else {
return
}
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "TryScan" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("downstream", downstream),
("status", status),
("result", result)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -5,6 +5,10 @@
// Created by Sergej Jaskiewicz on 19.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publishers {
/// A publisher that publishes a given sequence of elements.
@@ -25,11 +29,13 @@ extension Publishers {
self.sequence = sequence
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure,
Elements.Element == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Elements.Element == Downstream.Input
{
if let inner = Inner(downstream: subscriber, sequence: sequence) {
var iterator = sequence.makeIterator()
if iterator.next() != nil {
let inner = Inner(downstream: subscriber, sequence: sequence)
subscriber.receive(subscription: inner)
} else {
subscriber.receive(subscription: Subscriptions.empty)
@@ -44,87 +50,135 @@ extension Publishers.Sequence {
private final class Inner<Downstream: Subscriber, Elements: Sequence, Failure>
: Subscription,
CustomStringConvertible,
CustomReflectable
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Elements.Element,
Downstream.Failure == Failure
{
// NOTE: This class has been audited for thread-safety
typealias Iterator = Elements.Iterator
typealias Element = Elements.Element
private var _downstream: Downstream?
private var _sequence: Elements?
private var _iterator: Iterator?
private var _nextValue: Element?
private var sequence: Elements?
private var downstream: Downstream?
private var iterator: Iterator
private var next: Element?
private var pendingDemand = Subscribers.Demand.none
private var recursion = false
private var lock = UnfairLock.allocate()
init?(downstream: Downstream, sequence: Elements) {
fileprivate init(downstream: Downstream, sequence: Elements) {
self.sequence = sequence
self.downstream = downstream
self.iterator = sequence.makeIterator()
next = iterator.next()
}
// Early exit if the sequence is empty
var iterator = sequence.makeIterator()
guard iterator.next() != nil else { return nil }
_downstream = downstream
_sequence = sequence
_iterator = sequence.makeIterator()
_nextValue = iterator.next()
deinit {
lock.deallocate()
}
var description: String {
return _sequence.map(String.init(describing:)) ?? "Sequence"
return sequence.map(String.init(describing:)) ?? "Sequence"
}
var customMirror: Mirror {
let children: CollectionOfOne<(label: String?, value: Any)> =
CollectionOfOne(("sequence", _sequence ?? [Element]()))
let children =
CollectionOfOne<Mirror.Child>(("sequence", sequence ?? [Element]()))
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard downstream != nil else {
lock.unlock()
return
}
pendingDemand += demand
if recursion {
lock.unlock()
return
}
guard let downstream = _downstream else { return }
while let downstream = self.downstream, pendingDemand > 0 {
if let current = self.next {
pendingDemand -= 1
var demand = demand
while demand > 0 {
if let nextValue = _nextValue {
demand += downstream.receive(nextValue)
demand -= 1
// Combine calls next() while the lock is held.
// It is possible to engineer a custom Sequence that would cause
// a deadlock here, but it would be something insane.
let next = iterator.next()
recursion = true
lock.unlock()
let additionalDemand = downstream.receive(current)
lock.lock()
recursion = false
pendingDemand += additionalDemand
self.next = next
}
_nextValue = _iterator?.next()
if _nextValue == nil {
_downstream?.receive(completion: .finished)
cancel()
break
if next == nil {
self.downstream = nil
self.sequence = nil
lock.unlock()
downstream.receive(completion: .finished)
return
}
}
lock.unlock()
}
func cancel() {
_downstream = nil
_iterator = nil
_sequence = nil
lock.lock()
downstream = nil
sequence = nil
lock.unlock()
}
}
}
extension Publishers.Sequence: Equatable where Elements: Equatable {}
extension Publishers.Sequence where Failure == Never {
public func min(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.min(by: areInIncreasingOrder))
}
public func max(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.max(by: areInIncreasingOrder))
}
public func first(
where predicate: (Elements.Element) -> Bool
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.first(where: predicate))
}
}
extension Publishers.Sequence {
public func allSatisfy(
_ predicate: (Elements.Element) -> Bool
) -> Publishers.Once<Bool, Failure> {
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(sequence.allSatisfy(predicate))
}
public func tryAllSatisfy(
_ predicate: (Elements.Element) throws -> Bool
) -> Publishers.Once<Bool, Error> {
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try sequence.allSatisfy(predicate) })
}
public func collect() -> Publishers.Once<[Elements.Element], Failure> {
public func collect() -> Result<[Elements.Element], Failure>.OCombine.Publisher {
return .init(Array(sequence))
}
@@ -134,39 +188,15 @@ extension Publishers.Sequence {
return .init(sequence: sequence.compactMap(transform))
}
public func min(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.min(by: areInIncreasingOrder))
}
public func tryMin(
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.min(by: areInIncreasingOrder) })
}
public func max(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.max(by: areInIncreasingOrder))
}
public func tryMax(
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.max(by: areInIncreasingOrder) })
}
public func contains(
where predicate: (Elements.Element) -> Bool
) -> Publishers.Once<Bool, Failure> {
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(sequence.contains(where: predicate))
}
public func tryContains(
where predicate: (Elements.Element) throws -> Bool
) -> Publishers.Once<Bool, Error> {
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try sequence.contains(where: predicate) })
}
@@ -182,26 +212,14 @@ extension Publishers.Sequence {
return .init(sequence: sequence.dropFirst(count))
}
public func first(
where predicate: (Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.first(where: predicate))
}
public func tryFirst(
where predicate: (Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.first(where: predicate) })
}
public func filter(
_ isIncluded: (Elements.Element) -> Bool
) -> Publishers.Sequence<[Elements.Element], Failure> {
return .init(sequence: sequence.filter(isIncluded))
}
public func ignoreOutput() -> Publishers.Empty<Elements.Element, Failure> {
return .init(completeImmediately: true)
public func ignoreOutput() -> Empty<Elements.Element, Failure> {
return .init()
}
public func map<ElementOfResult>(
@@ -225,7 +243,7 @@ extension Publishers.Sequence {
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: @escaping (Accumulator, Elements.Element) -> Accumulator
) -> Publishers.Once<Accumulator, Failure> {
) -> Result<Accumulator, Failure>.OCombine.Publisher {
return .init(sequence.reduce(initialResult, nextPartialResult))
}
@@ -233,7 +251,7 @@ extension Publishers.Sequence {
_ initialResult: Accumulator,
_ nextPartialResult:
@escaping (Accumulator, Elements.Element) throws -> Accumulator
) -> Publishers.Once<Accumulator, Error> {
) -> Result<Accumulator, Error>.OCombine.Publisher {
return .init(Result { try sequence.reduce(initialResult, nextPartialResult) })
}
@@ -276,38 +294,43 @@ extension Publishers.Sequence where Elements.Element: Equatable {
return .init(sequence: result)
}
public func contains(_ output: Elements.Element) -> Publishers.Once<Bool, Failure> {
public func contains(
_ output: Elements.Element
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(sequence.contains(output))
}
}
extension Publishers.Sequence where Elements.Element: Comparable {
extension Publishers.Sequence where Failure == Never, Elements.Element: Comparable {
public func min() -> Publishers.Optional<Elements.Element, Failure> {
public func min() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.min())
}
public func max() -> Publishers.Optional<Elements.Element, Failure> {
public func max() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.max())
}
}
extension Publishers.Sequence where Elements: Collection, Failure == Never {
public func first() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.first)
}
public func output(
at index: Elements.Index
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
}
extension Publishers.Sequence where Elements: Collection {
public func first() -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.first)
}
public func count() -> Publishers.Once<Int, Failure> {
public func count() -> Result<Int, Failure>.OCombine.Publisher {
return .init(sequence.count)
}
public func output(
at index: Elements.Index
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
public func output(
in range: Range<Elements.Index>
) -> Publishers.Sequence<[Elements.Element], Failure> {
@@ -315,40 +338,41 @@ extension Publishers.Sequence where Elements: Collection {
}
}
extension Publishers.Sequence where Elements: BidirectionalCollection {
extension Publishers.Sequence where Elements: BidirectionalCollection, Failure == Never {
public func last() -> Publishers.Optional<Elements.Element, Failure> {
public func last() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.last)
}
public func last(
where predicate: (Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.last(where: predicate))
}
}
public func tryLast(
where predicate: (Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.last(where: predicate) })
extension Publishers.Sequence where Elements: RandomAccessCollection, Failure == Never {
public func output(
at index: Elements.Index
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
public func count() -> Just<Int> {
return .init(sequence.count)
}
}
extension Publishers.Sequence where Elements: RandomAccessCollection {
public func output(
at index: Elements.Index
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
public func output(
in range: Range<Elements.Index>
) -> Publishers.Sequence<[Elements.Element], Failure> {
return .init(sequence: Array(sequence[range]))
}
public func count() -> Publishers.Optional<Int, Failure> {
public func count() -> Result<Int, Failure>.OCombine.Publisher {
return .init(sequence.count)
}
}
@@ -408,7 +432,7 @@ extension Publishers.Sequence where Elements: RangeReplaceableCollection {
extension Sequence {
public func publisher() -> Publishers.Sequence<Self, Never> {
return Publishers.Sequence(sequence: self)
public var publisher: Publishers.Sequence<Self, Never> {
return .init(sequence: self)
}
}
@@ -0,0 +1,99 @@
//
// Publishers.SetFailureType.swift
//
//
// Created by Sergej Jaskiewicz on 08.07.2019.
//
extension Publishers {
/// A publisher that appears to send a specified failure type.
///
/// The publisher cannot actually fail with the specified type and instead
/// just finishes normally. Use this publisher type when you need to match
/// the error types for two mismatched publishers.
public struct SetFailureType<Upstream: Publisher, Failure: Error>: Publisher
where Upstream.Failure == Never
{
public typealias Output = Upstream.Output
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// Creates a publisher that appears to send a specified failure type.
///
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
public init(upstream: Upstream) {
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
upstream.subscribe(Inner(downstream: subscriber))
}
public func setFailureType<NewFailure: Error>(
to failure: NewFailure.Type
) -> Publishers.SetFailureType<Upstream, NewFailure> {
return .init(upstream: upstream)
}
}
}
extension Publishers.SetFailureType: Equatable where Upstream: Equatable {}
extension Publisher where Failure == Never {
/// Changes the failure type declared by the upstream publisher.
///
/// The publisher returned by this method cannot actually fail
/// with the specified type and instead just finishes normally. Instead, you use
/// this method when you need to match the error types of two mismatched publishers.
///
/// - Parameter failureType: The `Failure` type presented by this publisher.
/// - Returns: A publisher that appears to send the specified failure type.
public func setFailureType<NewFailure: Error>(
to failureType: NewFailure.Type
) -> Publishers.SetFailureType<Self, NewFailure> {
return .init(upstream: self)
}
}
extension Publishers.SetFailureType {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Failure == Downstream.Failure
{
private let downstream: Downstream
let combineIdentifier = CombineIdentifier()
fileprivate init(downstream: Downstream) {
self.downstream = downstream
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Never>) {
downstream.receive(completion: .finished)
}
var description: String { return "SetFailureType" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,53 @@
//
// Publishers.Share
//
//
// Created by Sergej Jaskiewicz on 18/09/2019.
//
extension Publisher {
/// Returns a publisher as a class instance.
///
/// The downstream subscriber receieves elements and completion states unchanged from
/// the upstream publisher. Use this operator when you want to use
/// reference semantics, such as storing a publisher instance in a property.
///
/// - Returns: A class instance that republishes its upstream publisher.
public func share() -> Publishers.Share<Self> {
return .init(upstream: self)
}
}
extension Publishers {
/// A publisher implemented as a class, which otherwise behaves like its upstream
/// publisher.
public final class Share<Upstream: Publisher>: Publisher, Equatable {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
private typealias MulticastSubject = PassthroughSubject<Output, Failure>
private let inner: Autoconnect<Multicast<Upstream, MulticastSubject>>
public let upstream: Upstream
public init(upstream: Upstream) {
self.inner = upstream.multicast(subject: .init()).autoconnect()
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
inner.subscribe(subscriber)
}
public static func == (lhs: Share, rhs: Share) -> Bool {
return lhs === rhs
}
}
}
@@ -0,0 +1,192 @@
//
// Publishers.SubscribeOn.swift
//
//
// Created by Sergej Jaskiewicz on 02.12.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
extension Publisher {
/// Specifies the scheduler on which to perform subscribe, cancel, and request
/// operations.
///
/// In contrast with `receive(on:options:)`, which affects downstream messages,
/// `subscribe(on:)` changes the execution context of upstream messages.
/// In the following example, requests to `jsonPublisher` are performed on
/// `backgroundQueue`, but elements received from it are performed on `RunLoop.main`.
///
/// let ioPerformingPublisher == // Some publisher.
/// let uiUpdatingSubscriber == // Some subscriber that updates the UI.
///
/// ioPerformingPublisher
/// .subscribe(on: backgroundQueue)
/// .receiveOn(on: RunLoop.main)
/// .subscribe(uiUpdatingSubscriber)
///
/// - Parameters:
/// - scheduler: The scheduler on which to receive upstream messages.
/// - options: Options that customize the delivery of elements.
/// - Returns: A publisher which performs upstream operations on the specified
/// scheduler.
public func subscribe<Context: Scheduler>(
on scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.SubscribeOn<Self, Context> {
return .init(upstream: self, scheduler: scheduler, options: options)
}
}
extension Publishers {
/// A publisher that receives elements from an upstream publisher on a specific
/// scheduler.
public struct SubscribeOn<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The scheduler the publisher should use to receive elements.
public let scheduler: Context
/// Scheduler options that customize the delivery of elements.
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.upstream = upstream
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
scheduler.schedule(options: options) {
self.upstream.subscribe(Inner(self, downstream: subscriber))
}
}
}
}
extension Publishers.SubscribeOn {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias SubscribeOn = Publishers.SubscribeOn<Upstream, Context>
private enum State {
case ready(SubscribeOn, Downstream)
case subscribed(SubscribeOn, Downstream, Subscription)
case terminal
}
private let lock = UnfairLock.allocate()
private var state: State
private let upstreamLock = UnfairLock.allocate()
init(_ subscribeOn: SubscribeOn, downstream: Downstream) {
state = .ready(subscribeOn, downstream)
}
deinit {
lock.deallocate()
upstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case let .ready(subscribeOn, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscribeOn, downstream, subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(_, downstream, _) = state else {
lock.unlock()
return .none
}
lock.unlock()
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case let .subscribed(_, downstream, _) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(subscribeOn, _, subscription) = state else {
lock.unlock()
return
}
lock.unlock()
subscribeOn.scheduler.schedule(options: subscribeOn.options) { [weak self] in
self?.scheduledRequest(demand, subscription: subscription)
}
}
private func scheduledRequest(_ demand: Subscribers.Demand,
subscription: Subscription) {
upstreamLock.lock()
subscription.request(demand)
upstreamLock.unlock()
}
func cancel() {
lock.lock()
guard case let .subscribed(subscribeOn, _, subscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subscribeOn.scheduler.schedule(options: subscribeOn.options) { [weak self] in
self?.scheduledCancel(subscription)
}
}
private func scheduledCancel(_ subscription: Subscription) {
upstreamLock.lock()
subscription.cancel()
upstreamLock.unlock()
}
var description: String { return "SubscribeOn" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
+230
View File
@@ -0,0 +1,230 @@
//
// Record.swift
//
//
// Created by Sergej Jaskiewicz on 12.11.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
/// A publisher that allows for recording a series of inputs and a completion for later
/// playback to each subscriber.
public struct Record<Output, Failure: Error>: Publisher {
/// The recorded output and completion.
public let recording: Recording
/// Interactively record a series of outputs and a completion.
public init(record: (inout Recording) -> Void) {
var recording = Recording()
record(&recording)
self.init(recording: recording)
}
/// Initialize with a recording.
public init(recording: Recording) {
self.recording = recording
}
/// Set up a complete recording with the specified output and completion.
public init(output: [Output], completion: Subscribers.Completion<Failure>) {
self.init(recording: Recording(output: output, completion: completion))
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
if recording.output.isEmpty {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: recording.completion)
} else {
let inner = Inner(downstream: subscriber,
sequence: recording.output,
completion: recording.completion)
subscriber.receive(subscription: inner)
}
}
/// A recorded set of `Output` and a `Subscribers.Completion`.
public struct Recording {
public typealias Input = Output
private enum State {
case input
case complete
}
private var state: State
/// The output which will be sent to a `Subscriber`.
public private(set) var output: [Output]
/// The completion which will be sent to a `Subscriber`.
public private(set) var completion: Subscribers.Completion<Failure>
/// Set up a recording in a state ready to receive output.
public init() {
state = .input
output = []
completion = .finished
}
/// Set up a complete recording with the specified output and completion.
public init(output: [Output],
completion: Subscribers.Completion<Failure> = .finished) {
self.state = .complete
self.output = output
self.completion = completion
}
/// Add an output to the recording.
///
/// A `fatalError` will be raised if output is added after adding completion.
public mutating func receive(_ input: Input) {
precondition(state == .input,
"Receiving values after completion is not allowed")
output.append(input)
}
/// Add a completion to the recording.
///
/// A `fatalError` will be raised if more than one completion is added.
public mutating func receive(completion: Subscribers.Completion<Failure>) {
precondition(state == .input,
"Receiving completion more than once is not allowed")
self.completion = completion
self.state = .complete
}
}
}
extension Record: Codable where Output: Codable, Failure: Codable {}
extension Record.Recording: Codable where Output: Codable, Failure: Codable {
private enum CodingKeys: String, CodingKey {
case output = "output"
case completion = "completion"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let output = try container.decode([Output].self, forKey: .output)
let completion = try container.decode(Subscribers.Completion<Failure>.self,
forKey: .completion)
self.init(output: output, completion: completion)
}
public func encode(into encoder: Encoder) throws {
try encode(to: encoder)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(output, forKey: .output)
try container.encode(completion, forKey: .completion)
}
}
extension Record {
// This class is almost the same as Publishers.Sequence.Inner
// despite some small details.
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
// NOTE: This class has been audited for thread-safety
private var sequence: [Output]?
private let completion: Subscribers.Completion<Failure>
private var downstream: Downstream?
private var iterator: IndexingIterator<[Output]>
private var next: Output?
private var pendingDemand = Subscribers.Demand.none
private var recursion = false
private var lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream,
sequence: [Output],
completion: Subscribers.Completion<Failure>) {
self.sequence = sequence
self.completion = completion
self.downstream = downstream
self.iterator = sequence.makeIterator()
next = iterator.next()
}
deinit {
lock.deallocate()
}
var description: String {
lock.lock()
defer { lock.unlock() }
return sequence.map { $0.description } ?? "Cancelled Events"
}
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("sequence", sequence ?? [Output]()),
("completion", completion)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard downstream != nil else {
lock.unlock()
return
}
pendingDemand += demand
if recursion {
lock.unlock()
return
}
while let downstream = self.downstream, pendingDemand > 0 {
if let current = self.next {
pendingDemand -= 1
let next = iterator.next()
recursion = true
lock.unlock()
let additionalDemand = downstream.receive(current)
lock.lock()
recursion = false
pendingDemand += additionalDemand
self.next = next
}
if next == nil {
self.downstream = nil
self.sequence = nil
lock.unlock()
downstream.receive(completion: completion)
return
}
}
lock.unlock()
}
func cancel() {
lock.lock()
downstream = nil
sequence = nil
lock.unlock()
}
}
}
@@ -0,0 +1,338 @@
//
// Result.Publisher.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Result {
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
///
/// Combine extends `Result` with a nested type `Publisher`.
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
/// e. g. when importing Foundation), you will not be able
/// to write `Result<Int, Error>.Publisher`,
/// because Swift is unable to understand which `Publisher` you're referring to.
///
/// So you have to write `Result<Int, Error>.OCombine.Publisher`.
///
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
///
/// You can omit this whenever Combine is not available (e. g. on Linux).
public struct OCombine {
fileprivate let result: Result
fileprivate init(_ result: Result) {
self.result = result
}
public var publisher: Publisher {
return Publisher(result)
}
/// A publisher that publishes an output to each subscriber exactly once then
/// finishes, or fails immediately without producing any elements.
///
/// If `result` is `.success`, then `Once` waits until it receives a request for
/// at least 1 value before sending the output. If `result` is `.failure`,
/// then `Once` sends the failure immediately upon subscription.
///
/// In contrast with `Just`, a `Once` publisher can terminate with an error
/// instead of sending a value. In contrast with `Optional`, a `Once` publisher
/// always sends one value (unless it terminates with an error).
public struct Publisher: OpenCombine.Publisher {
public typealias Output = Success
/// The result to deliver to each subscriber.
public let result: Result
/// Creates a publisher that delivers the specified result.
///
/// If the result is `.success`, the `Once` publisher sends the specified
/// output to all subscribers and finishes normally. If the result is
/// `.failure`, then the publisher fails immediately with the specified
/// error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ result: Result) {
self.result = result
}
/// Creates a publisher that sends the specified output to all subscribers and
/// finishes normally.
///
/// - Parameter output: The output to deliver to each subscriber.
public init(_ output: Output) {
self.init(.success(output))
}
/// Creates a publisher that immediately terminates upon subscription with
/// the given failure.
///
/// - Parameter failure: The failure to send when terminating.
public init(_ failure: Failure) {
self.init(.failure(failure))
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Success, Downstream.Failure == Failure
{
switch result {
case .success(let value):
subscriber.receive(subscription: Inner(value: value,
downstream: subscriber))
case .failure(let failure):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(failure))
}
}
}
}
public var ocombine: OCombine {
return OCombine(self)
}
#if !canImport(Combine)
/// A publisher that publishes an output to each subscriber exactly once then
/// finishes, or fails immediately without producing any elements.
///
/// If `result` is `.success`, then `Once` waits until it receives a request for
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
/// sends the failure immediately upon subscription.
///
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
/// value (unless it terminates with an error).
public typealias Publisher = OCombine.Publisher
public var publisher: Publisher {
return Publisher(self)
}
#endif
}
extension Result.OCombine {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Success, Downstream.Failure == Failure
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
private var downstream: Downstream?
private let output: Success
init(value: Success, downstream: Downstream) {
self.output = value
self.downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
guard let downstream = self.downstream else { return }
self.downstream = nil
_ = downstream.receive(output)
downstream.receive(completion: .finished)
}
func cancel() {
downstream = nil
}
var description: String { return "Once" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
}
var playgroundDescription: Any { return description }
}
}
extension Result.OCombine.Publisher: Equatable
where Output: Equatable, Failure: Equatable
{
}
extension Result.OCombine.Publisher where Output: Equatable {
public func contains(_ output: Output) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(result.map { $0 == output })
}
public func removeDuplicates() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
}
extension Result.OCombine.Publisher where Output: Comparable {
public func min() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func max() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
}
extension Result.OCombine.Publisher {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(result.map(predicate))
}
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(result.tryMap(predicate))
}
public func contains(
where predicate: (Output) -> Bool
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(result.map(predicate))
}
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(result.tryMap(predicate))
}
public func collect() -> Result<[Output], Failure>.OCombine.Publisher {
return .init(result.map { [$0] })
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Result<Output, Error>.OCombine.Publisher {
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
}
public func max(
by areInIncreasingOrder: (Output, Output
) -> Bool) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Result<Output, Error>.OCombine.Publisher {
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
}
public func count() -> Result<Int, Failure>.OCombine.Publisher {
return .init(result.map { _ in 1 })
}
public func first() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func last() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func ignoreOutput() -> Empty<Output, Failure> {
return .init()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
return .init(result.map(transform))
}
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(result.tryMap(transform))
}
public func mapError<TransformedFailure: Error>(
_ transform: (Failure) -> TransformedFailure
) -> Result<Output, TransformedFailure>.OCombine.Publisher {
return .init(result.mapError(transform))
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Result<Output, Error>.OCombine.Publisher {
return .init(result.tryMap { _ = try predicate($0, $0); return $0 })
}
public func replaceError(
with output: Output
) -> Result<Output, Never>.OCombine.Publisher {
return .init(.success(result.unwrapOr(output)))
}
public func replaceEmpty(
with output: Output
) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func retry(_ times: Int) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Result<Accumulator, Failure>.OCombine.Publisher {
return .init(result.map { nextPartialResult(initialResult, $0) })
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Result<Accumulator, Error>.OCombine.Publisher{
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
return .init(result.map { nextPartialResult(initialResult, $0) })
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
}
}
extension Result.OCombine.Publisher where Failure == Never {
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Result<Output, Failure>.OCombine.Publisher {
return .init(result.success)
}
}
-16
View File
@@ -44,19 +44,3 @@ extension Result where Failure == Never {
}
}
}
/// An overload of `catching` that takes a non-thowing function and returns
/// a function that returns an always succeeding `Result.`
internal func catching<Input, Output, Failure: Error>(
_ transform: @escaping (Input) -> Output
) -> (Input) -> Result<Output, Failure> {
return { input in .success(transform(input)) }
}
/// Takes a function that may throw an error and returns a function that doesn't throw
/// an error but returns `Result`.
internal func catching<Input, Output>(
_ transform: @escaping (Input) throws -> Output
) -> (Input) -> Result<Output, Error> {
return { input in Result { try transform(input) } }
}
+4
View File
@@ -22,6 +22,10 @@ public protocol Subject: AnyObject, Publisher {
/// - Parameter completion: A `Completion` instance which indicates whether publishing
/// has finished normally or failed with an error.
func send(completion: Subscribers.Completion<Failure>)
/// Provides this Subject an opportunity to establish demand for any new upstream
/// subscriptions (say, via `Publisher.subscribe<S: Subject>(_: Subject)`)
func send(subscription: Subscription)
}
extension Subject where Output == Void {
@@ -13,21 +13,24 @@ extension Subscribers {
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
public typealias Failure = Never
public private(set) var object: Root?
public let keyPath: ReferenceWritableKeyPath<Root, Input>
private var _upstreamSubscription: Subscription?
private var status = SubscriptionStatus.awaitingSubscription
public var description: String { return "Assign \(Root.self)." }
public var customMirror: Mirror {
let children: [(label: String?, value: Any)] = [
(label: "object", value: object as Any),
(label: "keyPath", value: keyPath),
(label: "upstreamSubscription", value: _upstreamSubscription as Any)
let children: [Mirror.Child] = [
("object", object as Any),
("keyPath", keyPath),
("status", status as Any)
]
return Mirror(self, children: children)
}
@@ -40,17 +43,21 @@ extension Subscribers {
}
public func receive(subscription: Subscription) {
if _upstreamSubscription == nil {
_upstreamSubscription = subscription
subscription.request(.unlimited)
} else {
switch status {
case .subscribed, .terminal:
subscription.cancel()
case .awaitingSubscription:
status = .subscribed(subscription)
subscription.request(.unlimited)
}
}
public func receive(_ value: Input) -> Subscribers.Demand {
if _upstreamSubscription != nil {
switch status {
case .subscribed:
object?[keyPath: keyPath] = value
case .awaitingSubscription, .terminal:
break
}
return .none
}
@@ -60,22 +67,26 @@ extension Subscribers {
}
public func cancel() {
_upstreamSubscription?.cancel()
_upstreamSubscription = nil
guard case let .subscribed(subscription) = status else {
return
}
subscription.cancel()
status = .terminal
object = nil
}
}
}
extension Publisher where Self.Failure == Never {
extension Publisher where Failure == Never {
/// Assigns the value of a KVO-compliant property from a publisher.
/// Assigns each element from a Publisher to a property on an object.
///
/// - Parameters:
/// - keyPath: The key path of the property to assign.
/// - object: The object on which to assign the value.
/// - Returns: A cancellable instance; used when you end KVO-based assignment of
/// the key paths value.
/// - Returns: A cancellable instance; used when you end assignment
/// of the received value. Deallocation of the result will tear down
/// the subscription stream.
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Output>,
on object: Root) -> AnyCancellable {
let subscriber = Subscribers.Assign(object: object, keyPath: keyPath)

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