210 Commits

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

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

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

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

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

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

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

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

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

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

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

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

Error checks are essential, since the client code may bay be crafted
in such a way that causes a non-recursive lock to be recursively
acuired, which is undefined behavior with std::mutex.
2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 7a9e8b22d2 Bump iOS version for compatibility tests in CircleCI config 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 26f9acd75a Reimplement FlatMap to make all the tests pass 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 4c42b434ca Add more tests for FlatMap 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 8afe945325 Test that CombineIdentifier uses UInt64 under the hood 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz eb879213ef Automatically confirm apt install 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz e402fb3980 Remove redundant Self 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 4f96378c02 Remove OperatorSubscription class 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 243f3d200e Implement PrefixWhile, TryPrefixWhile using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz f1fb5552b5 Implement RemoveDuplicates, TryRemoveDuplicates using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz bfd875ccba Simplify CompactMap and TryCompactMap using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 27da28f378 Simplify Filter and TryFilter using FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz cba3a69e74 Implement FilterProducer 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz d634a76c39 A more robust test for Subscribers.Assign 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz b5e31a43ef Fix ReplaceError subscription behavior 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 1bb3583a36 More tests for contract violations in operators
- test the behavior when a value arrives earlier than the subscription
- test the behavior when a completion arrives earlier than
  the subscription
- test the behavior when requesting before a subscription arrives
- test the behavior when cancelling before a subscription arrives
2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz 449b8eef48 Test that Record doesn't finilize its Recording 2019-11-19 18:38:01 +03:00
Sergej Jaskiewicz d4bdd83a00 Implement the Record publisher 2019-11-12 17:31:29 +03:00
Sergej Jaskiewicz f1cc94adff Implement Publishers.Output 2019-11-11 20:26:31 +03:00
Sergej Jaskiewicz 5e1e10a780 Add CircleCI badge 2019-11-10 15:00:50 +03:00
Sergej Jaskiewicz 5b7358111c Remove redundant 2019-11-10 15:00:50 +03:00
Sergej Jaskiewicz 5f90c4c85f Add Gemfile.lock 2019-11-10 15:00:50 +03:00
Sergej Jaskiewicz 0dec30fcad Switch from Travis CI to CircleCI.
CircleCI has significantly faster builds. They also update their build
machines sooner than Travis CI.
2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz a5ec9723e2 Make the tests pass on 32-bit platforms 2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz 25ac4dfa5f Fix SwiftLint crash
https://github.com/realm/SwiftLint/issues/2793
2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz f5645f605b Remove old benchmarking infrastructure 2019-11-09 15:26:50 +03:00
Sergej Jaskiewicz ffef3ac76c Update CI configuration to run compatibility tests on Xcode 11.2 2019-10-31 14:01:44 +03:00
Sergej Jaskiewicz 36a9f1999c They fix bugs in Combine, we update our awesome test suite!
The bug fixes happened in macOS 10.15.1
2019-10-31 14:01:44 +03:00
Sergej Jaskiewicz f069f9b9fa Audit DropWhile/TryDropWhile for thread safety (#87)
Add more tests, make them pass
2019-10-25 15:45:40 +03:00
Sergej Jaskiewicz 55bdbba0f9 Publishers.Print: cancel new subscription if Inner is already subscribed (#92) 2019-10-24 15:11:55 +03:00
Sergej Jaskiewicz 1290545c49 Audit IgnoreOutput for thread safety, add more tests, make them pass (#88) 2019-10-23 23:44:59 +03:00
Sergej Jaskiewicz aed074af43 Add tests for Subscriptions.empty 2019-10-23 20:30:46 +03:00
Sergej Jaskiewicz 11ec7c89e6 Gybify Encode/Decode, add more tests, make them pass 2019-10-23 20:30:46 +03:00
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
232 changed files with 40107 additions and 5991 deletions
+274
View File
@@ -0,0 +1,274 @@
version: 2
jobs:
"Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)":
macos:
xcode: "11.3.0"
environment:
SWIFT_VERSION: "5.1.3"
steps:
- checkout
- run:
name: Building and running tests in debug mode with coverage
command: |
make test-debug \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--enable-code-coverage --build-path .build-test-debug"
xcrun llvm-cov show \
-instr-profile=.build-test-debug/debug/codecov/default.profdata \
.build-test-debug/debug/OpenCombinePackageTests.xctest/Contents/MacOS/OpenCombinePackageTests \
> coverage.txt
- run:
name: Building and running tests in debug mode with TSan
command: |
make test-debug-sanitize-thread \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--build-path .build-test-debug-sanitize-thread"
- run:
name: Building and running tests in release mode
command: |
make test-release \
SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
SWIFT_TEST_FLAGS="--build-path .build-test-release"
- run:
name: Generating Xcode project
command: make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
- run:
name: Building for testing on macOS 10.15.0 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-sdk macosx10.15 \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing on macOS 10.15.0 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-sdk macosx10.15 \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash) -D DerivedData
"Execute compatibility tests on iOS 13.6 (Xcode 11.6.0, Swift 5.2.4)":
macos:
xcode: "11.6.0"
environment:
SWIFT_VERSION: "5.2.4"
steps:
- checkout
- run:
name: Generating Xcode project
command: make generate-compatibility-xcodeproj
- run:
name: Building for testing on iOS 13.6 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.6" \
-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.6 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 11,OS=13.6" \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
"Execute tests on iOS 9.3 (Xcode 10.2.1, Swift 5.0.1)":
macos:
xcode: "10.2.1"
environment:
BUNDLE_PATH: .bundle # path to install gems and use for caching
SWIFT_VERSION: "5.0.1"
steps:
- checkout
- run:
name: Installing gem dependencies
command: bundle install && bundle clean
- restore_cache:
keys:
- v1-simulator-cache-{{ arch }}
- run:
# CircleCI doesn't have an iOS 9 simulator, so we need to install it manually.
name: Installing iOS 9 simulator
command: |
bundle exec xcversion simulators --install="iOS 9.3"
bundle exec xcversion simulators
xcrun simctl list
- save_cache:
key: v1-simulator-cache-{{ arch }}
paths:
- ~/Library/Caches/XcodeInstall
- run:
name: Generating Xcode project
command: |
make generate-xcodeproj SWIFT_BUILD_FLAGS="-Xswiftc -warnings-as-errors"
xcodebuild -scheme OpenCombine-Package -showdestinations
- run:
name: Building for testing on iOS 9.3 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild build-for-testing \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
-derivedDataPath DerivedData \
| tee xcodebuild_build-for-testing.log \
| xcpretty
- store_artifacts:
path: xcodebuild_build-for-testing.log
- run:
name: Testing on iOS 9.3 with xcodebuild
command: |
set -o pipefail \
&& xcodebuild test-without-building \
-scheme OpenCombine-Package \
-destination "platform=iOS Simulator,name=iPhone 4s,OS=9.3" \
-derivedDataPath DerivedData \
| tee xcodebuild_test-without-building.log \
| xcpretty --report junit -o build/reports/results.xml
- store_artifacts:
path: xcodebuild_test-without-building.log
- store_test_results:
path: build/reports
- run:
name: Uploading code coverage
command: |
bash <(curl -s https://codecov.io/bash) -D DerivedData
"Execute tests on Ubuntu 18.04 (Swift 5.2)":
docker:
- image: swift:5.2-bionic
environment:
SWIFT_VERSION: "5.2"
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_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
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_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
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_BUILD_FLAGS="-Xswiftc -warnings-as-errors" \
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.3.0"
environment:
HOMEBREW_NO_AUTO_UPDATE: "1"
steps:
- checkout
- run:
name: Install SwiftLint
command: |
brew install swiftlint
- run:
name: Install danger-swift
command: |
brew install danger/tap/danger-swift
- run:
name: Run danger-swift
command: danger-swift ci
"Run Pod spec lint":
macos:
xcode: "11.3.0"
environment:
HOMEBREW_NO_AUTO_UPDATE: "1"
steps:
- checkout
- run:
name: Pod lib lint
command: |
pod lib lint --allow-warnings --verbose
workflows:
version: 2
"OpenCombine: execute tests on macOS":
jobs:
- "Execute tests on macOS 10.15.0 (Xcode 11.3.0, Swift 5.1.3)"
"OpenCombine: execute compatibility tests":
jobs:
- "Execute compatibility tests on iOS 13.6 (Xcode 11.6.0, Swift 5.2.4)"
"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.2)"
"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/
+39 -1
View File
@@ -16,11 +16,14 @@ disabled_rules:
- identifier_name
- nesting
- notification_center_detachment
- no_fallthrough_only
- no_space_in_method_call
- redundant_string_enum_value
- todo
- trailing_comma
- type_body_length
- opening_brace
- untyped_error_in_catch
opt_in_rules:
- array_init
@@ -59,11 +62,14 @@ opt_in_rules:
- unneeded_parentheses_in_closure_argument
- untyped_error_in_catch
- unused_import
- unused_private_declaration
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- yoda_condition
implicit_return:
included:
- closure
line_length:
warning: 90
error: 120
@@ -75,3 +81,35 @@ generic_type_name:
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>
-58
View File
@@ -1,58 +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" OPENCOMBINE_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | Tests"
os: osx
osx_image: xcode10.2
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES" OPENCOMBINE_TEST="YES"
# - name: "iOS 13.0 | Swift 5.1 | Compatibility Tests"
# os: osx
# osx_image: xcode11
# env: SWIFT_VERSION="5.1" OPENCOMBINE_COMPATIBILITY_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | 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 [[ $OPENCOMBINE_TEST == "YES" ]]; then
swift test -c debug --enable-code-coverage --sanitize thread;
fi
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
swift test -c release;
fi
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
swift package generate-xcodeproj --xcconfig-overrides iOS-Combine-Compatibility.xcconfig;
set -o pipefail && xcodebuild -scheme OpenCombine-Package -sdk iphonesimulator13.0 -destination "platform=iOS Simulator,name=iPhone Xs,OS=13.0" build test | xcpretty;
fi
- if [[ $SWIFT_LINT == "YES" ]]; then
swiftlint lint --strict --reporter "emoji";
fi
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);
fi
+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.71.0)
faraday (0.17.0)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.134.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.23.9)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.3.2)
google-cloud-env (~> 1.0)
google-cloud-env (1.2.1)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
google-api-client (~> 0.23)
google-cloud-core (~> 1.2)
googleauth (>= 0.6.2, < 0.10.0)
googleauth (0.6.7)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
json (2.3.1)
jwt (2.1.0)
memoist (0.16.1)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mini_magick (4.9.5)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.3.0)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcode-install (2.6.2)
claide (>= 0.9.1, < 1.1.0)
fastlane (>= 2.1.0, < 3.0.0)
xcodeproj (1.13.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
xcode-install
BUNDLED WITH
2.0.1
+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.10.1"
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.10.1"
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.9'
end
+25
View File
@@ -0,0 +1,25 @@
Pod::Spec.new do |spec|
spec.name = "OpenCombineFoundation"
spec.version = "0.10.1"
spec.summary = "OpenCombine + OpenCombineFoundation interoperability"
spec.description = <<-DESC
Adds publishers to Foundation types like NotificationCenter, URLSession etc.
DESC
spec.homepage = "https://github.com/broadwaylamb/OpenCombine/"
spec.license = "MIT"
spec.authors = { "Sergej Jaskiewicz" => "jaskiewiczs@icloud.com" }
spec.source = { :git => "https://github.com/broadwaylamb/OpenCombine.git", :tag => "#{spec.version}" }
spec.swift_version = "5.0"
spec.osx.deployment_target = "10.10"
spec.ios.deployment_target = "8.0"
spec.watchos.deployment_target = "2.0"
spec.tvos.deployment_target = "9.0"
spec.source_files = "Sources/OpenCombineFoundation/**/*.swift"
spec.dependency "OpenCombine", '>= 0.9'
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
}
+13 -6
View File
@@ -6,13 +6,20 @@ let package = Package(
name: "OpenCombine",
products: [
.library(name: "OpenCombine", targets: ["OpenCombine"]),
],
dependencies: [
.package(url: "https://github.com/broadwaylamb/GottaGoFast.git", from: "0.1.0")
.library(name: "OpenCombineDispatch", targets: ["OpenCombineDispatch"]),
.library(name: "OpenCombineFoundation", targets: ["OpenCombineFoundation"]),
],
targets: [
.target(name: "OpenCombine"),
.target(name: "COpenCombineHelpers"),
.target(name: "OpenCombine", dependencies: ["COpenCombineHelpers"]),
.target(name: "OpenCombineDispatch", dependencies: ["OpenCombine"]),
.target(name: "OpenCombineFoundation", dependencies: ["OpenCombine",
"COpenCombineHelpers"]),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine", "GottaGoFast"])
]
dependencies: ["OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
],
cxxLanguageStandard: .cxx1z
)
+103 -6
View File
@@ -1,8 +1,10 @@
# OpenCombine
[![Build Status](https://travis-ci.org/broadwaylamb/OpenCombine.svg?branch=master)](https://travis-ci.org/broadwaylamb/OpenCombine)
[![codecov](https://codecov.io/gh/broadwaylamb/OpenCombine/branch/master/graph/badge.svg)](https://codecov.io/gh/broadwaylamb/OpenCombine)
[![OpenCombine](https://circleci.com/gh/OpenCombine/OpenCombine.svg?style=svg)](https://circleci.com/gh/OpenCombine/OpenCombine)
[![codecov](https://codecov.io/gh/OpenCombine/OpenCombine/branch/master/graph/badge.svg)](https://codecov.io/gh/OpenCombine/OpenCombine)
![Language](https://img.shields.io/badge/Swift-5.0-orange.svg)
![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg)
![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,20 +12,115 @@ The main goal of this project is to provide a compatible, reliable and efficient
The project is in early development.
### Installation
`OpenCombine` contains three public targets: `OpenCombine`, `OpenCombineFoundation` and `OpenCombineDispatch` (the fourth one, `COpenCombineHelpers`, is considered private. Don't import it in your projects).
OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using `DispatchQueue` as `Scheduler` for operators like `debounce`, `receive(on:)` etc.), you will need to import both `OpenCombine` and `OpenCombineDispatch`. The same applies to Foundation: if you want to use, for instance, `NotificationCenter` or `URLSession` publishers, you'll need to also import `OpenCombineFoundation`
##### 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/OpenCombine/OpenCombine.git", from: "0.10.1")
],
targets: [
.target(name: "MyAwesomePackage", dependencies: ["OpenCombine",
"OpenCombineDispatch",
"OpenCombineFoundation"])
]
```
###### 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/OpenCombine/OpenCombine.git), choose the latest available version, and activate the checkboxes:
<p align="center">
<img alt="Select the OpenCombine and OpenCombineDispatch targets"
src="https://user-images.githubusercontent.com/16309982/67618468-bd379f80-f7f8-11e9-917f-e76e878a1aee.png" width="70%">
</p>
##### CocoaPods
To add `OpenCombine` to a project using [CocoaPods](https://cocoapods.org/), add `OpenCombine` and `OpenCombineDispatch` to the list of target dependencies in your `Podfile`.
```ruby
pod 'OpenCombine', '~> 0.10.1'
pod 'OpenCombineDispatch', '~> 0.10.1'
pod 'OpenCombineFoundation', '~> 0.10.1'
```
### Contributing
In order to work on this project you will need Xcode 10.2 and Swift 5.0 or later.
Please refer to the [issue #1](https://github.com/broadwaylamb/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/broadwaylamb/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
Please refer to the [issue #1](https://github.com/OpenCombine/OpenCombine/issues/1) for the list of operators that remain unimplemented, as well as the [RemainingCombineInterface.swift](https://github.com/OpenCombine/OpenCombine/blob/master/RemainingCombineInterface.swift) file. The latter contains the generated interface of Apple's Combine from the latest Xcode 11 version. When the functionality is implemented in OpenCombine, it should be removed from the RemainingCombineInterface.swift file.
You can refer to [this gist](https://gist.github.com/broadwaylamb/c2c8550d76b3ff851c4c1dbf0a872e26) to observe Apple's Combine API changes between different Xcode (beta) versions, or to [this gist](https://gist.github.com/broadwaylamb/82dc2ce4ffbe06527c2c352b8f10910f) to see the relevant contents of the .swiftinterface file for Combine.
You can refer to [this repo](https://github.com/OpenCombine/combine-interfaces) to observe Apple's Combine API and documentation changes between different Xcode (beta) versions.
You can run compatibility tests against Apple's Combine. In order to do that you will need either macOS 10.14 with iOS 13 simulator installed (since the only way we can get Apple's Combine on macOS 10.14 is using the simulator), or macOS 10.15 (Apple's Combine is bundled with the OS). Execute the following command from the root of the package:
```
$ 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.
> NOTE: Before starting to work on some feature, please consult the [GitHub project](https://github.com/OpenCombine/OpenCombine/projects/2) to make sure that nobody's already making progress on the same feature! If not, then please create a draft PR to indicate that you're beginning your work.
#### Releasing a new version
1. Create a new branch from master and call it `release/<major>.<minor>.<patch>`.
1. Replace the usages of the old version in `README.md` with the new version (make sure to check the [Swift Package Manager](#swift-package-manager) and [CocoaPods](#cocoapods) sections).
1. Bump the version in `OpenCombine.podspec`, `OpenCombineDispatch.podspec` and `OpenCombineFoundation.podspec`. In the latter two you will also need to set the `spec.dependency "OpenCombine"` property to the **previous** version. Why? Because otherwise the `pod lib lint` command that we run on our regular CI will fail when validating the `OpenCombineDispatch` and `OpenCombineFoundation` podspecs, since the dependencies are not yet in the trunk. If we set the dependencies to the previous version (which is already in the trunk), everything will be fine. This is purely to make the CI work. The clients will not experience any issues, since the version is specified as `>=`.
1. Create a pull request to master for the release branch and make sure the CI passes.
1. Merge the pull request.
1. In the GitHub web interface on the [releases](https://github.com/OpenCombine/OpenCombine/releases) page, click the **Draft a new release** button.
1. The **Tag version** and **Release title** fields should be filled with the version number.
1. The description of the release should be consistent with the previous releases. It is a good practice to divide the description into several sections: additions, bugfixes, known issues etc. Also, be sure to mention the nicknames of the contributors of the new release.
1. Publish the release.
1. Switch to the master branch and pull the changes.
1. Push the release to CocoaPods trunk. For that, execute the following commands:
```
pod trunk push OpenCombine.podspec --verbose --allow-warnings
pod trunk push OpenCombineDispatch.podspec --verbose --allow-warnings
pod trunk push OpenCombineFoundation.podspec --verbose --allow-warnings
```
Note that you need to be one of the owners of the pod for that.
#### GYB
Some publishers in OpenCombine (like `Publishers.MapKeyPath`, `Publishers.Merge`) exist in several
different flavors in order to support several arities. For example, there are also `Publishers.MapKeyPath2`
and `Publishers.MapKeyPath3`, which are very similar but different enough that Swift's type system
can't help us here (because there's no support for variadic generics). Maintaining multiple instances of
those generic types is tedious and error-prone (they can get out of sync), so we use the GYB tool for
generating those instances from a template.
GYB is a Python script that evaluates Python code written inside a template file, so it's very flexible —
templates can be arbitrarily complex. There is a good article about GYB on
[NSHipster](https://nshipster.com/swift-gyb/).
GYB is part of the [Swift Open Source Project](https://github.com/apple/swift/blob/master/utils/gyb.py)
and can be distributed under the same license as Swift itself.
GYB template files have the `.gyb` extension. Run `make gyb` to generate Swift code from those
templates. The generated files are prefixed with `GENERATED-` and are checked into source control. Those
files should never be edited directly. Instead, the `.gyb` template should be edited, and after that the files
should be regenerated using `make gyb`.
#### Debugger Support
The file `opencombine_lldb.py` defines some `lldb` type summaries for easier debugging. These type summaries improve the way `lldb` and Xcode display some OpenCombine values.
To use `opencombine_lldb.py`, figure out its full path. Let's say the full path is `~/projects/OpenCombine/opencombine_lldb.py`. Then the following statement to your `~/.lldbinit` file:
command script import ~/projects/OpenCombine/opencombine_lldb.py
Currently, `opencombine_lldb.py` defines type summaries for these types:
- `Subscribers.Demand`
- That's all for now.
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
// From /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/swift/Foundation.swiftmodule/x86_64.swiftinterface
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.1.1 (swiftlang-1100.8.275.1 clang-1100.0.32.1)
// swift-module-flags: -target x86_64-apple-macosx10.15 -enable-objc-interop -autolink-force-load -enable-library-evolution -module-link-name swiftFoundation -swift-version 5 -O -enforce-exclusivity=unchecked -module-name Foundation
public typealias Published = Combine.Published
public typealias ObservableObject = Combine.ObservableObject
public protocol _KeyValueCodingAndObservingPublishing {
}
extension NSObject : Foundation._KeyValueCodingAndObservingPublishing {
}
extension _KeyValueCodingAndObservingPublishing where Self : ObjectiveC.NSObject {
public func publisher<Value>(for keyPath: Swift.KeyPath<Self, Value>, options: Foundation.NSKeyValueObservingOptions = [.initial, .new]) -> ObjectiveC.NSObject.KeyValueObservingPublisher<Self, Value>
}
extension NSObject.KeyValueObservingPublisher {
public func didChange() -> Combine.Publishers.Map<ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, Swift.Void>
}
extension NSObject {
public struct KeyValueObservingPublisher<Subject, Value> : Swift.Equatable where Subject : ObjectiveC.NSObject {
public let object: Subject
public let keyPath: Swift.KeyPath<Subject, Value>
public let options: Foundation.NSKeyValueObservingOptions
public init(object: Subject, keyPath: Swift.KeyPath<Subject, Value>, options: Foundation.NSKeyValueObservingOptions)
public static func == (lhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>, rhs: ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>) -> Swift.Bool
}
}
extension NSObject.KeyValueObservingPublisher : Combine.Publisher {
public typealias Output = Value
public typealias Failure = Swift.Never
public func receive<S>(subscriber: S) where Value == S.Input, S : Combine.Subscriber, S.Failure == ObjectiveC.NSObject.KeyValueObservingPublisher<Subject, Value>.Failure
}
@@ -0,0 +1,243 @@
//
// COpenCombineHelpers.cpp
//
//
// Created by Sergej Jaskiewicz on 23/09/2019.
//
#include "COpenCombineHelpers.h"
#include <atomic>
#include <cstdlib>
#include <system_error>
#include <pthread.h>
#include <signal.h>
#ifdef __APPLE__
#include <os/lock.h>
#endif // __APPLE__
// Throwing exceptions through language boundaries is undefined behavior,
// so we must catch all of them in our extern "C" functions.
#define OPENCOMBINE_HANDLE_EXCEPTION_BEGIN try {
// 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);
}
void opencombine_stop_in_debugger(void) {
raise(SIGTRAP);
}
} // extern "C"
@@ -0,0 +1,79 @@
//
// COpenCombineHelpers.h
//
//
// Created by Sergej Jaskiewicz on 23/09/2019.
//
#ifndef COPENCOMBINEHELPERS_H
#define COPENCOMBINEHELPERS_H
#include <stdint.h>
#if __has_attribute(swift_name)
# define OPENCOMBINE_SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
#else
# define OPENCOMBINE_SWIFT_NAME(_name)
#endif
#ifdef __cplusplus
extern "C" {
#endif
#pragma mark - CombineIdentifier
uint64_t opencombine_next_combine_identifier(void)
OPENCOMBINE_SWIFT_NAME(__nextCombineIdentifier());
#pragma mark - OpenCombineUnfairLock
/// A wrapper around an opaque pointer for type safety in Swift.
typedef struct OpenCombineUnfairLock {
void* _Nonnull opaque;
} OPENCOMBINE_SWIFT_NAME(__UnfairLock) OpenCombineUnfairLock;
/// Allocates a lock object. The allocated object must be destroyed by calling
/// the destroy() method.
OpenCombineUnfairLock opencombine_unfair_lock_alloc(void)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.allocate());
void opencombine_unfair_lock_lock(OpenCombineUnfairLock)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.lock(self:));
void opencombine_unfair_lock_unlock(OpenCombineUnfairLock)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.unlock(self:));
void opencombine_unfair_lock_assert_owner(OpenCombineUnfairLock mutex)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.assertOwner(self:));
void opencombine_unfair_lock_dealloc(OpenCombineUnfairLock lock)
OPENCOMBINE_SWIFT_NAME(__UnfairLock.deallocate(self:));
#pragma mark - OpenCombineUnfairRecursiveLock
/// A wrapper around an opaque pointer for type safety in Swift.
typedef struct OpenCombineUnfairRecursiveLock {
void* _Nonnull opaque;
} OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock) OpenCombineUnfairRecursiveLock;
OpenCombineUnfairRecursiveLock opencombine_unfair_recursive_lock_alloc(void)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.allocate());
void opencombine_unfair_recursive_lock_lock(OpenCombineUnfairRecursiveLock)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.lock(self:));
void opencombine_unfair_recursive_lock_unlock(OpenCombineUnfairRecursiveLock)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.unlock(self:));
void opencombine_unfair_recursive_lock_dealloc(OpenCombineUnfairRecursiveLock lock)
OPENCOMBINE_SWIFT_NAME(__UnfairRecursiveLock.deallocate(self:));
#pragma mark - Breakpoint
void opencombine_stop_in_debugger(void) OPENCOMBINE_SWIFT_NAME(__stopInDebugger());
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* COPENCOMBINEHELPERS_H */
+2 -1
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)?
@@ -56,7 +57,7 @@ extension AnyCancellable {
/// Stores this AnyCancellable in the specified set.
/// Parameters:
/// - collection: The set to store this AnyCancellable.
/// - set: The set to store this AnyCancellable.
public func store(in set: inout Set<AnyCancellable>) {
set.insert(self)
}
+20 -9
View File
@@ -5,6 +5,18 @@
// 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.
@inlinable
public func eraseToAnyPublisher() -> AnyPublisher<Output, Failure> {
return .init(self)
}
}
/// A type-erasing publisher.
///
/// Use `AnyPublisher` to wrap a publisher whose type has details you dont want to expose
@@ -13,7 +25,6 @@ public struct AnyPublisher<Output, Failure: Error>
: CustomStringConvertible,
CustomPlaygroundDisplayConvertible
{
@usableFromInline
internal let box: PublisherBoxBase<Output, Failure>
@@ -47,8 +58,8 @@ extension AnyPublisher: Publisher {
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
@inlinable
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
box.subscribe(subscriber)
}
@@ -62,11 +73,11 @@ internal class PublisherBoxBase<Output, Failure: Error>: Publisher {
@inlinable
internal init() {}
@inlinable
internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
@usableFromInline
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
fatalError()
abstractMethod()
}
}
@@ -84,8 +95,8 @@ internal final class PublisherBox<PublisherType: Publisher>
}
@inlinable
override internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
override internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
base.subscribe(subscriber)
}
+5 -54
View File
@@ -62,8 +62,8 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
} else if let desccription = subscriber as? CustomStringConvertible {
playgroundDescriptionThunk = { desccription.description }
} else if let description = subscriber as? CustomStringConvertible {
playgroundDescriptionThunk = { description.description }
} else {
let fixedDescription = String(describing: type(of: subscriber))
playgroundDescriptionThunk = { fixedDescription }
@@ -137,17 +137,17 @@ internal class AnySubscriberBase<Input, Failure: Error>: Subscriber {
@usableFromInline
internal func receive(subscription: Subscription) {
fatalError()
abstractMethod()
}
@usableFromInline
internal func receive(_ input: Input) -> Subscribers.Demand {
fatalError()
abstractMethod()
}
@usableFromInline
internal func receive(completion: Subscribers.Completion<Failure>) {
fatalError()
abstractMethod()
}
}
@@ -222,52 +222,3 @@ internal final class ClosureBasedAnySubscriber<Input, Failure: Error>
receiveCompletionThunk(completion)
}
}
internal final class SubjectSubscriber<Downstream: Subject>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
Subscription
{
internal var downstreamSubject: Downstream?
internal var upstreamSubscription: Subscription?
internal init(_ parent: Downstream) {
self.downstreamSubject = parent
}
internal func receive(subscription: Subscription) {
guard upstreamSubscription == nil else { return }
upstreamSubscription = subscription
downstreamSubject?.send(subscription: self)
}
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
downstreamSubject?.send(input)
return .none
}
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
downstreamSubject?.send(completion: completion)
}
internal var description: String { return "Subject" }
internal var customMirror: Mirror {
let children: [(label: String?, value: Any)] = [
(label: "downstreamSubject", value: downstreamSubject as Any),
(label: "upstreamSubscription", value: upstreamSubscription as Any)
]
return Mirror(self, children: children)
}
internal func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
internal func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
downstreamSubject = nil
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ extension Cancellable {
/// Stores this Cancellable in the specified set.
/// Parameters:
/// - collection: The set to store this Cancellable.
/// - set: 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))"
}
}
+183 -82
View File
@@ -9,21 +9,29 @@
/// changes.
public final class CurrentValueSubject<Output, Failure: Error>: Subject {
private let _lock = Lock(recursive: true)
private let lock = UnfairLock.allocate()
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
private var active = true
private var _completion: Subscribers.Completion<Failure>?
private var completion: Subscribers.Completion<Failure>?
internal var upstreamSubscriptions: [Subscription] = []
private var downstreams = ConduitList<Output, Failure>.empty
internal var hasAnyDownstreamDemand = false
private var currentValue: Output
private var upstreamSubscriptions: [Subscription] = []
/// The value wrapped by this subject, published as a new element whenever it changes.
public var value: Output {
didSet {
send(value)
get {
lock.lock()
defer { lock.unlock() }
return currentValue
}
set {
lock.lock()
currentValue = newValue
sendValueAndConsumeLock(newValue)
}
}
@@ -31,120 +39,213 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
///
/// - Parameter value: The initial value to publish.
public init(_ value: Output) {
self.value = value
self.currentValue = value
}
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
for subscription in upstreamSubscriptions {
subscription.cancel()
}
lock.deallocate()
}
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
subscription.request(.unlimited)
}
lock.lock()
upstreamSubscriptions.append(subscription)
lock.unlock()
subscription.request(.unlimited)
}
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Output == Subscriber.Input, Failure == Subscriber.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
lock.lock()
if active {
let conduit = Conduit(parent: self, downstream: subscriber)
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
} else {
let completion = self.completion!
lock.unlock()
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
}
}
public func send(_ input: Output) {
_lock.do {
for subscription in _subscriptions where !subscription.isCompleted {
if subscription._demand > 0 {
subscription._offer(input)
subscription._demand -= 1
} else {
subscription._delivered = false
}
}
lock.lock()
sendValueAndConsumeLock(input)
}
private func sendValueAndConsumeLock(_ newValue: Output) {
#if DEBUG
lock.assertOwner()
#endif
guard active else {
lock.unlock()
return
}
currentValue = newValue
let downstreams = self.downstreams
lock.unlock()
downstreams.forEach { conduit in
conduit.offer(newValue)
}
}
public func send(completion: Subscribers.Completion<Failure>) {
_completion = completion
_lock.do {
for subscriber in _subscriptions {
subscriber._receive(completion: completion)
}
lock.lock()
guard active else {
lock.unlock()
return
}
active = false
self.completion = completion
let downstreams = self.downstreams
self.downstreams.removeAll()
lock.unlock()
downstreams.forEach { conduit in
conduit.finish(completion: completion)
}
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
guard active else {
lock.unlock()
return
}
downstreams.remove(conduit)
lock.unlock()
}
}
extension CurrentValueSubject {
fileprivate class Conduit: Subscription {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
fileprivate var _parent: CurrentValueSubject?
fileprivate var parent: CurrentValueSubject?
fileprivate var _downstream: AnySubscriber<Output, Failure>?
fileprivate var downstream: Downstream?
fileprivate var _demand: Subscribers.Demand = .none
fileprivate var demand = Subscribers.Demand.none
/// Whethere we satisfied the demand
fileprivate var _delivered = false
private var lock = UnfairLock.allocate()
var isCompleted: Bool {
return _parent == nil
}
private var downstreamLock = UnfairRecursiveLock.allocate()
fileprivate func _offer(_ value: Output) {
let newDemand = _downstream?.receive(value) ?? .none
_demand += newDemand
_delivered = true
}
private var deliveredCurrentValue = false
fileprivate init(parent: CurrentValueSubject,
downstream: AnySubscriber<Output, Failure>) {
_parent = parent
_downstream = downstream
downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
override func offer(_ output: Output) {
lock.lock()
guard demand > 0, let downstream = self.downstream else {
deliveredCurrentValue = false
lock.unlock()
return
}
demand -= 1
deliveredCurrentValue = true
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(output)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
demand += newDemand
lock.unlock()
}
func request(_ demand: Subscribers.Demand) {
precondition(demand > 0)
_parent?._lock.do {
if !_delivered, let value = _parent?.value {
_offer(value)
_demand += demand
_demand -= 1
} else {
_demand = demand
}
_parent?.hasAnyDownstreamDemand = true
override func finish(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func cancel() {
_parent = nil
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
if deliveredCurrentValue {
self.demand += demand
lock.unlock()
return
}
// Hasn't yet delivered the current value
self.demand += demand
deliveredCurrentValue = true
if let currentValue = self.parent?.value {
self.demand -= 1
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(currentValue)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
self.demand += newDemand
}
lock.unlock()
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "CurrentValueSubject" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("demand", demand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension CurrentValueSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "CurrentValueSubject" }
}
+202
View File
@@ -0,0 +1,202 @@
//
// Future.swift
//
//
// Created by Max Desiatov on 24/11/2019.
//
/// A publisher that eventually produces one value and then finishes or fails.
public final class Future<Output, Failure: Error>: Publisher {
public typealias Promise = (Result<Output, Failure>) -> Void
private let lock = UnfairLock.allocate()
private var downstreams = ConduitList<Output, Failure>.empty
private var result: Result<Output, Failure>?
public init(
_ attemptToFulfill: @escaping (@escaping Promise) -> Void
) {
attemptToFulfill(self.promise)
}
deinit {
lock.deallocate()
}
private func promise(_ result: Result<Output, Failure>) {
lock.lock()
guard self.result == nil else {
lock.unlock()
return
}
self.result = result
let downstreams = self.downstreams
self.downstreams.removeAll()
lock.unlock()
switch result {
case .success(let output):
downstreams.forEach { $0.offer(output) }
case .failure(let error):
downstreams.forEach { $0.finish(completion: .failure(error)) }
}
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
let conduit = Conduit(parent: self, downstream: subscriber)
lock.lock()
if let result = self.result {
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
conduit.fulfill(result)
} else {
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
}
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
downstreams.remove(conduit)
lock.unlock()
}
}
extension Future {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
fileprivate var parent: Future?
fileprivate var downstream: Downstream?
fileprivate var hasAnyDemand = false
private var lock = UnfairLock.allocate()
private var downstreamLock = UnfairRecursiveLock.allocate()
fileprivate init(parent: Future, downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
fileprivate func fulfill(_ result: Result<Output, Failure>) {
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
let parent = self.parent
if case .success = result, !hasAnyDemand {
lock.unlock()
return
}
self.downstream = nil
self.parent = nil
lock.unlock()
downstreamLock.lock()
switch result {
case .success(let output):
_ = downstream.receive(output)
downstream.receive(completion: .finished)
case .failure(let error):
downstream.receive(completion: .failure(error))
}
downstreamLock.unlock()
parent?.disassociate(self)
}
override func offer(_ output: Output) {
fulfill(.success(output))
}
override func finish(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
assertionFailure("unreachable")
case .failure(let error):
fulfill(.failure(error))
}
}
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard let downstream = self.downstream, let parent = self.parent else {
lock.unlock()
return
}
hasAnyDemand = true
parent.lock.lock()
guard let result = parent.result else {
parent.lock.unlock()
lock.unlock()
return
}
parent.lock.unlock()
self.downstream = nil
self.parent = nil
lock.unlock()
downstreamLock.lock()
switch result {
case .success(let output):
_ = downstream.receive(output)
downstream.receive(completion: .finished)
case .failure(let error):
// This branch is not reachable under normal circumstances,
// but may be reachable in case of a race condition.
downstream.receive(completion: .failure(error))
}
downstreamLock.unlock()
parent.disassociate(self)
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "Future" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("hasAnyDemand", hasAnyDemand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,40 @@
//
// ConduitBase.swift
//
//
// Created by Sergej Jaskiewicz on 25.06.2020.
//
internal class ConduitBase<Output, Failure: Error>: Subscription {
internal init() {}
internal func offer(_ output: Output) {
abstractMethod()
}
internal func finish(completion: Subscribers.Completion<Failure>) {
abstractMethod()
}
internal func request(_ demand: Subscribers.Demand) {
abstractMethod()
}
internal func cancel() {
abstractMethod()
}
}
extension ConduitBase: Equatable {
internal static func == (lhs: ConduitBase<Output, Failure>,
rhs: ConduitBase<Output, Failure>) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
}
extension ConduitBase: Hashable {
internal func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
@@ -0,0 +1,57 @@
//
// ConduitList.swift
//
//
// Created by Sergej Jaskiewicz on 25.06.2020.
//
internal enum ConduitList<Output, Failure: Error> {
case empty
case single(ConduitBase<Output, Failure>)
case many(Set<ConduitBase<Output, Failure>>)
}
extension ConduitList {
internal mutating func insert(_ conduit: ConduitBase<Output, Failure>) {
switch self {
case .empty:
self = .single(conduit)
case .single(conduit):
break // This element already exists.
case .single(let existingConduit):
self = .many([existingConduit, conduit])
case .many(var set):
set.insert(conduit)
self = .many(set)
}
}
internal func forEach(
_ body: (ConduitBase<Output, Failure>) throws -> Void
) rethrows {
switch self {
case .empty:
break
case .single(let conduit):
try body(conduit)
case .many(let set):
try set.forEach(body)
}
}
internal mutating func remove(_ conduit: ConduitBase<Output, Failure>) {
switch self {
case .single(conduit):
self = .empty
case .empty, .single:
break
case .many(var set):
set.remove(conduit)
self = .many(set)
}
}
internal mutating func removeAll() {
self = .empty
}
}
@@ -0,0 +1,176 @@
//
// FilterProducer.swift
//
//
// Created by Sergej Jaskiewicz on 23.10.2019.
//
/// A helper class that acts like both subscriber and subscription.
///
/// Filter-like operators send an instance of their `Inner` class that is subclass
/// of this class to the upstream publisher (as subscriber) and
/// to the downstream subscriber (as subscription).
///
/// Filter-like operators include `Publishers.Filter`,
/// `Publishers.RemoveDuplicates`, `Publishers.PrefixWhile` and more.
///
/// Subclasses must override the `receive(newValue:)` and `description`.
internal class FilterProducer<Downstream: Subscriber,
Input,
Output,
UpstreamFailure: Error,
Filter>
: CustomStringConvertible,
CustomReflectable
where Downstream.Input == Output
{
// 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 }
}
+13
View File
@@ -0,0 +1,13 @@
//
// Locking.swift
//
//
// Created by Sergej Jaskiewicz on 11.06.2019.
//
#if canImport(COpenCombineHelpers)
import COpenCombineHelpers
#endif
internal typealias UnfairLock = __UnfairLock
internal typealias UnfairRecursiveLock = __UnfairRecursiveLock
@@ -0,0 +1,26 @@
//
// PartialCompletion.swift
//
//
// Created by Sergej Jaskiewicz on 22.09.2019.
//
/// A value of this type is returned by the overridden `receive(newValue:)` method
/// of the `ReduceProducer` and `FilterProducer` classes.
internal enum PartialCompletion<Value, Failure: Error> {
/// Indicate that we should continue accepting the upstream's output.
case `continue`(Value)
/// Indicate that no values should be received from the upstream anymore.
case finished
/// Indicate that there was a failure and we should send it downstream.
case failure(Failure)
}
extension PartialCompletion where Value == Void {
/// Indicate that we should continue accepting the upstream's output.
internal static var `continue`: PartialCompletion { return .continue(()) }
}
@@ -0,0 +1,250 @@
//
// ReduceProducer.swift
//
//
// Created by Sergej Jaskiewicz on 22.09.2019.
//
/// A helper class that acts like both subscriber and subscription.
///
/// Reduce-like operators send an instance of their `Inner` class that is subclass
/// of this class to the upstream publisher (as subscriber) and
/// to the downstream subscriber (as subscription).
///
/// Reduce-like operators include `Publishers.Reduce`, `Publishers.TryReduce`,
/// `Publishers.Count`, `Publishers.FirstWhere`, `Publishers.AllSatisfy` and more.
///
/// Subclasses must override the `receive(newValue:)` and `description`.
internal class ReduceProducer<Downstream: Subscriber,
Input,
Output,
UpstreamFailure: Error,
Reducer>
: CustomStringConvertible,
CustomReflectable
where Downstream.Input == Output
{
// NOTE: This class has been audited for thread safety
// MARK: - State
internal final var result: Output?
private let initial: Output?
internal final let reduce: Reducer
private var status = SubscriptionStatus.awaitingSubscription
private let downstream: Downstream
private let lock = UnfairLock.allocate()
private var downstreamRequested = false
private var cancelled = false
private var completed = false
private var upstreamCompleted = false
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,97 @@
//
// SubjectSubscriber.swift
//
//
// Created by Sergej Jaskiewicz on 16/09/2019.
//
// 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 subject = downstreamSubject, upstreamSubscription != nil else {
lock.unlock()
return .none
}
lock.unlock()
subject.send(input)
return .none
}
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
lock.lock()
guard let subject = downstreamSubject, upstreamSubscription != nil else {
lock.unlock()
return
}
lock.unlock()
subject.send(completion: completion)
downstreamSubject = nil
}
internal var description: String { return "Subject" }
internal var playgroundDescription: Any { return description }
internal var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstreamSubject", downstreamSubject as Any),
("upstreamSubscription", upstreamSubscription as Any),
("subject", downstreamSubject as Any)
]
return Mirror(self, children: children)
}
internal func request(_ demand: Subscribers.Demand) {
lock.lock()
guard let subscription = upstreamSubscription else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
internal func cancel() {
lock.lock()
guard !isCancelled, let subscription = upstreamSubscription else {
lock.unlock()
return
}
upstreamSubscription = nil
downstreamSubject = nil
lock.unlock()
subscription.cancel()
}
}
@@ -0,0 +1,23 @@
//
// SubscriptionStatus.swift
//
//
// Created by Sergej Jaskiewicz on 21.09.2019.
//
internal enum SubscriptionStatus {
case awaitingSubscription
case subscribed(Subscription)
case terminal
}
extension SubscriptionStatus {
internal var isAwaitingSubscription: Bool {
switch self {
case .awaitingSubscription:
return true
default:
return false
}
}
}
@@ -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()
}
}
+192
View File
@@ -0,0 +1,192 @@
//
// 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 lock = UnfairLock.allocate()
private var connections = Set<Conduit>()
// TODO: Combine needs this for some reason
private var identifier: ObjectIdentifier?
public init() {}
deinit {
lock.deallocate()
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Void, Downstream.Failure == Never
{
let inner = Inner(downstream: subscriber, parent: self)
lock.lock()
connections.insert(inner)
lock.unlock()
subscriber.receive(subscription: inner)
}
public func send() {
lock.lock()
let connections = self.connections
lock.unlock()
for connection in connections {
connection.send()
}
}
private func remove(_ conduit: Conduit) {
lock.lock()
connections.remove(conduit)
lock.unlock()
}
}
extension ObservableObjectPublisher {
private class Conduit: Hashable {
fileprivate func send() {
abstractMethod()
}
fileprivate static func == (lhs: Conduit, rhs: Conduit) -> Bool {
return lhs === rhs
}
fileprivate func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
private final class Inner<Downstream: Subscriber>
: Conduit,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Void, Downstream.Failure == Never
{
private enum State {
case initialized
case active
case terminal
}
private weak var parent: ObservableObjectPublisher?
private let downstream: Downstream
private let downstreamLock = UnfairRecursiveLock.allocate()
private let lock = UnfairLock.allocate()
private var state = State.initialized
init(downstream: Downstream, parent: ObservableObjectPublisher) {
self.parent = parent
self.downstream = downstream
}
deinit {
downstreamLock.deallocate()
lock.deallocate()
}
override func send() {
lock.lock()
let state = self.state
lock.unlock()
if state == .active {
downstreamLock.lock()
_ = downstream.receive()
downstreamLock.unlock()
}
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
if state == .initialized {
state = .active
}
lock.unlock()
}
func cancel() {
lock.lock()
state = .terminal
lock.unlock()
parent?.remove(self)
}
var description: String { return "ObservableObjectPublisher" }
var customMirror: Mirror {
let children = CollectionOfOne<Mirror.Child>(("downstream", downstream))
return Mirror(self, children: children)
}
var playgroundDescription: Any {
return description
}
}
}
+158 -71
View File
@@ -9,14 +9,15 @@
///
/// 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 {
public final class PassthroughSubject<Output, Failure: Error>: Subject {
private let _lock = Lock(recursive: true)
private let lock = UnfairLock.allocate()
private var _completion: Subscribers.Completion<Failure>?
private var active = true
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
private var completion: Subscribers.Completion<Failure>?
private var downstreams = ConduitList<Output, Failure>.empty
internal var upstreamSubscriptions: [Subscription] = []
@@ -25,111 +26,197 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
public init() {}
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
for subscription in upstreamSubscriptions {
subscription.cancel()
}
lock.deallocate()
}
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
if hasAnyDownstreamDemand {
subscription.request(.unlimited)
}
lock.lock()
upstreamSubscriptions.append(subscription)
let hasAnyDownstreamDemand = self.hasAnyDownstreamDemand
lock.unlock()
if hasAnyDownstreamDemand {
subscription.request(.unlimited)
}
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
lock.lock()
if active {
let conduit = Conduit(parent: self, downstream: subscriber)
downstreams.insert(conduit)
lock.unlock()
subscriber.receive(subscription: conduit)
} else {
let completion = self.completion!
lock.unlock()
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
}
}
public func send(_ input: Output) {
_lock.do {
for subscription in _subscriptions
where !subscription._isCompleted && subscription._demand > 0
{
let newDemand = subscription._downstream?.receive(input) ?? .none
subscription._demand += newDemand
subscription._demand -= 1
}
lock.lock()
guard active else {
lock.unlock()
return
}
let downstreams = self.downstreams
lock.unlock()
downstreams.forEach { conduit in
conduit.offer(input)
}
}
public func send(completion: Subscribers.Completion<Failure>) {
_lock.do {
_completion = completion
for subscriber in _subscriptions {
subscriber._receive(completion: completion)
}
lock.lock()
guard active else {
lock.unlock()
return
}
active = false
self.completion = completion
let downstreams = self.downstreams
self.downstreams.removeAll()
lock.unlock()
downstreams.forEach { conduit in
conduit.finish(completion: completion)
}
}
private func _acknowledgeDownstreamDemand() {
_lock.do {
guard !hasAnyDownstreamDemand else { return }
hasAnyDownstreamDemand = true
for subscription in upstreamSubscriptions {
subscription.request(.unlimited)
}
private func acknowledgeDownstreamDemand() {
lock.lock()
if hasAnyDownstreamDemand {
lock.unlock()
return
}
hasAnyDownstreamDemand = true
let upstreamSubscriptions = self.upstreamSubscriptions
lock.unlock()
for subscription in upstreamSubscriptions {
subscription.request(.unlimited)
}
}
private func disassociate(_ conduit: ConduitBase<Output, Failure>) {
lock.lock()
guard active else {
lock.unlock()
return
}
downstreams.remove(conduit)
lock.unlock()
}
}
extension PassthroughSubject {
fileprivate final class Conduit: Subscription {
private final class Conduit<Downstream: Subscriber>
: ConduitBase<Output, Failure>,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Failure
{
fileprivate var _parent: PassthroughSubject?
fileprivate var parent: PassthroughSubject?
fileprivate var _downstream: AnySubscriber<Output, Failure>?
fileprivate var downstream: Downstream?
fileprivate var _demand: Subscribers.Demand = .none
fileprivate var demand = Subscribers.Demand.none
fileprivate var _isCompleted: Bool {
return _parent == nil
}
private var lock = UnfairLock.allocate()
private var downstreamLock = UnfairRecursiveLock.allocate()
fileprivate init(parent: PassthroughSubject,
downstream: AnySubscriber<Output, Failure>) {
_parent = parent
_downstream = downstream
downstream: Downstream) {
self.parent = parent
self.downstream = downstream
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !_isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
override func offer(_ output: Output) {
lock.lock()
guard demand > 0, let downstream = self.downstream else {
lock.unlock()
return
}
demand -= 1
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(output)
downstreamLock.unlock()
guard newDemand > 0 else { return }
lock.lock()
demand += newDemand
lock.unlock()
}
fileprivate func request(_ demand: Subscribers.Demand) {
precondition(demand > 0, "demand must not be zero")
_parent?._lock.do {
_demand = demand
override func finish(completion: Subscribers.Completion<Failure>) {
lock.lock()
guard let downstream = self.downstream else {
lock.unlock()
return
}
_parent?._acknowledgeDownstreamDemand()
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
fileprivate func cancel() {
_parent = nil
override func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.demand += demand
let parent = self.parent
lock.unlock()
parent?.acknowledgeDownstreamDemand()
}
override func cancel() {
lock.lock()
if self.downstream == nil {
lock.unlock()
return
}
self.downstream = nil
let parent = self.parent
self.parent = nil
lock.unlock()
parent?.disassociate(self)
}
var description: String { return "PassthroughSubject" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("parent", parent as Any),
("downstream", downstream as Any),
("demand", demand),
("subject", parent as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension PassthroughSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "PassthroughSubject" }
}
+102
View File
@@ -0,0 +1,102 @@
//
// 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> {
@inlinable // trivially forwarding
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)
@@ -38,9 +38,9 @@ public struct Deferred<DeferredPublisher: Publisher>: Publisher {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure,
Output == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Output == Downstream.Input
{
let deferredPublisher = createPublisher()
deferredPublisher.subscribe(subscriber)
+2 -2
View File
@@ -42,8 +42,8 @@ public struct Empty<Output, Failure: Error>: Publisher, Equatable {
/// to the subscriber. If `false`, it never completes.
public let completeImmediately: Bool
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
if completeImmediately {
+2 -2
View File
@@ -30,8 +30,8 @@ public struct Fail<Output, Failure: Error>: Publisher {
/// The failure to send when terminating the publisher.
public let error: Failure
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Failure == Downstream.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(error))
@@ -0,0 +1,620 @@
//
//
// Auto-generated from GYB template. DO NOT EDIT!
//
//
//
//
// Publishers.Catch.swift
//
//
// Created by Sergej Jaskiewicz on 25.12.2019.
//
extension Publisher {
/// Handles errors from an upstream publisher by replacing it with another publisher.
///
/// The following example replaces any error from the upstream publisher and replaces
/// the upstream with a `Just` publisher. This continues the stream by publishing
/// a single value and completing normally.
/// ```
/// enum SimpleError: Error { case error }
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
/// if v < 5 {
/// return v
/// } else {
/// throw SimpleError.error
/// }
/// }
///
/// let noErrorPublisher = errorPublisher.catch { _ in
/// return Just(100)
/// }
/// ```
/// Backpressure note: This publisher passes through `request` and `cancel` to
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
/// demand to the new `Publisher`.
///
/// - Parameter handler: A closure that accepts the upstream failure as input and
/// returns a publisher to replace the upstream publisher.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher.
public func `catch`<NewPublisher: Publisher>(
_ handler: @escaping (Failure) -> NewPublisher
) -> Publishers.Catch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
/// Handles errors from an upstream publisher by either replacing it with another
/// publisher or `throw`ing a new error.
///
/// - Parameter handler: A `throw`ing closure that accepts the upstream failure as
/// input and returns a publisher to replace the upstream publisher or if an error
/// is thrown will send the error downstream.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher.
public func tryCatch<NewPublisher: Publisher>(
_ handler: @escaping (Failure) throws -> NewPublisher
) -> Publishers.TryCatch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
}
extension Publishers {
/// A publisher that handles errors from an upstream publisher by replacing the failed
/// publisher with another publisher.
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = NewPublisher.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that accepts the upstream failure as input and returns a publisher
/// to replace the upstream publisher.
public let handler: (Upstream.Failure) -> NewPublisher
/// Creates a publisher that handles errors from an upstream publisher by
/// replacing the failed publisher with another publisher.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - handler: A closure that accepts the upstream failure as input and returns
/// a publisher to replace the upstream publisher.
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
/// A publisher that handles errors from an upstream publisher by replacing the failed
/// publisher with another publisher or optionally producing a new error.
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = Error
public let upstream: Upstream
public let handler: (Upstream.Failure) throws -> NewPublisher
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
}
extension Publishers.Catch {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output,
Downstream.Failure == NewPublisher.Failure
{
struct UncaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePre(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePre(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
return inner.receivePre(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
struct CaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = NewPublisher.Output
typealias Failure = NewPublisher.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePost(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePost(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
inner.receivePost(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
private enum State {
case pendingPre
case pre(Subscription)
case pendingPost
case post(Subscription)
case cancelled
}
private let lock = UnfairLock.allocate()
private var demand = Subscribers.Demand.none
private var state = State.pendingPre
private let downstream: Downstream
private let handler: (Upstream.Failure) -> NewPublisher
init(downstream: Downstream,
handler: @escaping (Upstream.Failure) -> NewPublisher) {
self.downstream = downstream
self.handler = handler
}
deinit {
lock.deallocate()
}
func receivePre(subscription: Subscription) {
lock.lock()
guard case .pendingPre = state else {
lock.unlock()
subscription.cancel()
return
}
state = .pre(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
let newDemand = downstream.receive(input)
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
lock.lock()
switch state {
case .pre:
state = .cancelled
lock.unlock()
downstream.receive(completion: .finished)
case .pendingPre, .pendingPost, .post, .cancelled:
lock.unlock()
}
case .failure(let error):
lock.lock()
switch state {
case .pre:
state = .pendingPost
lock.unlock()
handler(error).subscribe(CaughtS(inner: self))
case .cancelled:
lock.unlock()
case .pendingPre, .post, .pendingPost:
completionBeforeSubscription()
}
}
}
func receivePost(subscription: Subscription) {
lock.lock()
guard case .pendingPost = state else {
lock.unlock()
subscription.cancel()
return
}
state = .post(subscription)
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
lock.lock()
guard case .post = state else {
lock.unlock()
return
}
state = .cancelled
lock.unlock()
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .pendingPre:
// The client is only able to call the `request` method after we've sent
// `self` downstream. We only do it in the `receivePre(subscription:)`
// method, after setting `state` to `pre`.
// After that `state` never becomes `pendingPre`.
requestBeforeSubscription()
case let .pre(subscription):
self.demand += demand
lock.unlock()
subscription.request(demand)
case .pendingPost:
self.demand += demand
lock.unlock()
case let .post(subscription):
lock.unlock()
subscription.request(demand)
case .cancelled:
lock.unlock()
}
}
func cancel() {
lock.lock()
switch state {
case let .pre(subscription), let .post(subscription):
state = .cancelled
lock.unlock()
subscription.cancel()
case .pendingPre, .pendingPost, .cancelled:
lock.unlock()
}
}
var description: String { return "Catch" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.TryCatch {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output,
Downstream.Failure == Error
{
struct UncaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePre(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePre(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
return inner.receivePre(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
struct CaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = NewPublisher.Output
typealias Failure = NewPublisher.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePost(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePost(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
inner.receivePost(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
private enum State {
case pendingPre
case pre(Subscription)
case pendingPost
case post(Subscription)
case cancelled
}
private let lock = UnfairLock.allocate()
private var demand = Subscribers.Demand.none
private var state = State.pendingPre
private let downstream: Downstream
private let handler: (Upstream.Failure) throws -> NewPublisher
init(downstream: Downstream,
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
self.downstream = downstream
self.handler = handler
}
deinit {
lock.deallocate()
}
func receivePre(subscription: Subscription) {
lock.lock()
guard case .pendingPre = state else {
lock.unlock()
subscription.cancel()
return
}
state = .pre(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
let newDemand = downstream.receive(input)
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
lock.lock()
switch state {
case .pre:
state = .cancelled
lock.unlock()
downstream.receive(completion: .finished)
case .pendingPre, .pendingPost, .post, .cancelled:
lock.unlock()
}
case .failure(let error):
lock.lock()
switch state {
case .pre:
state = .pendingPost
lock.unlock()
do {
try handler(error).subscribe(CaughtS(inner: self))
} catch let anotherError {
lock.lock()
state = .cancelled
lock.unlock()
downstream.receive(completion: .failure(anotherError))
}
case .cancelled:
lock.unlock()
case .pendingPre, .post, .pendingPost:
completionBeforeSubscription()
}
}
}
func receivePost(subscription: Subscription) {
lock.lock()
guard case .pendingPost = state else {
lock.unlock()
subscription.cancel()
return
}
state = .post(subscription)
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
lock.lock()
guard case .post = state else {
lock.unlock()
return
}
state = .cancelled
lock.unlock()
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .pendingPre:
// The client is only able to call the `request` method after we've sent
// `self` downstream. We only do it in the `receivePre(subscription:)`
// method, after setting `state` to `pre`.
// After that `state` never becomes `pendingPre`.
requestBeforeSubscription()
case let .pre(subscription):
self.demand += demand
lock.unlock()
subscription.request(demand)
case .pendingPost:
self.demand += demand
lock.unlock()
case let .post(subscription):
lock.unlock()
subscription.request(demand)
case .cancelled:
lock.unlock()
}
}
func cancel() {
lock.lock()
switch state {
case let .pre(subscription), let .post(subscription):
state = .cancelled
lock.unlock()
subscription.cancel()
case .pendingPre, .pendingPost, .cancelled:
lock.unlock()
}
}
var description: String { return "TryCatch" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
private func completionBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: received completion but do not have subscription",
file: file,
line: line)
}
private func requestBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: request before subscription sent",
file: file,
line: line)
}
@@ -0,0 +1,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 }
}
}
+55 -25
View File
@@ -25,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))
}
@@ -249,35 +249,65 @@ extension Just {
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(Result { try nextPartialResult(initialResult, output) })
}
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
public func prepend(_ elements: Output...) -> Publishers.Sequence<[Output], Never> {
return prepend(elements)
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
public func prepend<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Sequence<[Output], Never> where Output == Elements.Element {
return .init(sequence: elements + [output])
}
public func append(_ elements: Output...) -> Publishers.Sequence<[Output], Never> {
return append(elements)
}
public func append<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Sequence<[Output], Never> where Output == Elements.Element {
return .init(sequence: [output] + elements)
}
}
extension Just {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
private var downstream: Downstream?
private let value: Output
fileprivate init(value: Output, downstream: Downstream) {
self.downstream = downstream
self.value = value
}
}
func cancel() {
_downstream = nil
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
guard let downstream = self.downstream 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,25 +0,0 @@
//
// OperatorSubscription.swift
//
//
// Created by Sergej Jaskiewicz on 26.06.2019.
//
internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
internal var downstream: Downstream?
internal var upstreamSubscription: Subscription?
internal var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
internal init(downstream: Downstream) {
self.downstream = downstream
}
internal func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
downstream = nil
}
}
@@ -50,8 +50,8 @@ extension Optional {
self.output = output
}
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
{
if let output = output {
subscriber.receive(subscription: Inner(value: output,
@@ -74,34 +74,44 @@ extension Optional {
#endif
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
extension Optional.OCombine {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Wrapped
{
// NOTE: this class has been audited for thread safety.
// Combine doesn't use any locking here.
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
private var downstream: Downstream?
private let output: Wrapped
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
init(value: Wrapped, downstream: Downstream) {
self.output = value
self.downstream = downstream
}
}
func cancel() {
_downstream = nil
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
guard let downstream = self.downstream else { return }
self.downstream = nil
_ = downstream.receive(output)
downstream.receive(completion: .finished)
}
var description: String { return "Optional" }
func cancel() {
downstream = nil
}
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
var description: String { return "Optional" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(output))
}
var playgroundDescription: Any { return description }
}
}
@@ -138,7 +148,7 @@ extension Optional.OCombine.Publisher {
}
public func collect() -> Optional<[Output]>.OCombine.Publisher {
return .init(self.output.map { [$0] })
return .init(self.output.map { [$0] } ?? [])
}
public func compactMap<ElementOfResult>(
@@ -225,11 +235,13 @@ extension Optional.OCombine.Publisher {
in range: RangeExpression
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
let range = range.relative(to: 0 ..< Int.max)
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
precondition(range.lowerBound >= 0, "lowerBound must not be negative")
// I don't know why, but Combine has this precondition
precondition(range.upperBound < .max - 1)
return .init(output.flatMap { range.contains(0) ? $0 : nil })
return .init(
output.flatMap { (range.lowerBound == 0 && range.upperBound != 0) ? $0 : nil }
)
}
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
@@ -0,0 +1,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,132 @@
//
// Publishers.AssertNoFailure.swift
//
//
// Created by Sergej Jaskiewicz on 25.12.2019.
//
extension Publisher {
/// Raises a fatal error when its upstream publisher fails, and otherwise republishes
/// all received input.
///
/// Use this function for internal sanity checks that are active during testing but
/// do not impact performance of shipping code.
///
/// - Parameters:
/// - prefix: A string used at the beginning of the fatal error message.
/// - file: A filename used in the error message. This defaults to `#file`.
/// - line: A line number used in the error message. This defaults to `#line`.
/// - Returns: A publisher that raises a fatal error when its upstream publisher
/// fails.
public func assertNoFailure(_ prefix: String = "",
file: StaticString = #file,
line: UInt = #line) -> Publishers.AssertNoFailure<Self> {
return .init(upstream: self, prefix: prefix, file: file, line: line)
}
}
extension Publishers {
/// A publisher that raises a fatal error upon receiving any failure, and otherwise
/// republishes all received input.
///
/// Use this function for internal sanity checks that are active during testing but
/// do not impact performance of shipping code.
public struct AssertNoFailure<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Never
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The string used at the beginning of the fatal error message.
public let prefix: String
/// The filename used in the error message.
public let file: StaticString
/// The line number used in the error message.
public let line: UInt
public init(upstream: Upstream, prefix: String, file: StaticString, line: UInt) {
self.upstream = upstream
self.prefix = prefix
self.file = file
self.line = line
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Never
{
upstream.subscribe(Inner(downstream: subscriber,
prefix: prefix,
file: file,
line: line))
}
}
}
extension Publishers.AssertNoFailure {
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Never
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let prefix: String
private let file: StaticString
private let line: UInt
let combineIdentifier = CombineIdentifier()
init(downstream: Downstream, prefix: String, file: StaticString, line: UInt) {
self.downstream = downstream
self.prefix = prefix
self.file = file
self.line = line
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure(let error):
let prefix = self.prefix.isEmpty ? "" : self.prefix + ": "
fatalError("\(prefix)\(error)", file: file, line: line)
}
}
var description: String { return "AssertNoFailure" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("file", file),
("line", line),
("prefix", prefix)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,187 @@
//
// Publishers.Autoconnect.swift
//
//
// Created by Sergej Jaskiewicz on 18/09/2019.
//
extension ConnectablePublisher {
/// Automates the process of connecting or disconnecting from this connectable
/// publisher.
///
/// Use `autoconnect()` to simplify working with `ConnectablePublisher` instances,
/// such as 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,336 @@
//
// Publishers.Buffer.swift
//
//
// Created by Sergej Jaskiewicz on 08.01.2020.
//
extension Publisher {
/// Buffers elements received from an upstream publisher.
/// - Parameter size: The maximum number of elements to store.
/// - Parameter prefetch: The strategy for initially populating the buffer.
/// - Parameter whenFull: The action to take when the buffer becomes full.
public func buffer(
size: Int,
prefetch: Publishers.PrefetchStrategy,
whenFull: Publishers.BufferingStrategy<Failure>
) -> Publishers.Buffer<Self> {
return .init(upstream: self,
size: size,
prefetch: prefetch,
whenFull: whenFull)
}
}
extension Publishers {
/// A strategy for filling a buffer.
///
/// * keepFull: A strategy to fill the buffer at subscription time, and keep it full
/// thereafter.
/// * byRequest: A strategy that avoids prefetching and instead performs requests
/// on demand.
public enum PrefetchStrategy {
/// A strategy to fill the buffer at subscription time, and keep it full
/// thereafter.
case keepFull
/// A strategy that avoids prefetching and instead performs requests
/// on demand.
case byRequest
}
/// A strategy for handling exhaustion of a buffers capacity.
///
/// * dropNewest: When full, discard the newly-received element without buffering it.
/// * dropOldest: When full, remove the least recently-received element from the
/// buffer.
/// * customError: When full, execute the closure to provide a custom error.
public enum BufferingStrategy<Failure: Error> {
/// When full, discard the newly-received element without buffering it.
case dropNewest
/// When full, remove the least recently-received element from the buffer.
case dropOldest
/// When full, execute the closure to provide a custom error.
case customError(() -> Failure)
}
/// A publisher that buffers elements received from an upstream publisher.
public struct Buffer<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The maximum number of elements to store.
public let size: Int
/// The strategy for initially populating the buffer.
public let prefetch: Publishers.PrefetchStrategy
/// The action to take when the buffer becomes full.
public let whenFull: Publishers.BufferingStrategy<Failure>
/// Creates a publisher that buffers elements received from an upstream publisher.
/// - Parameter upstream: The publisher from which this publisher receives
/// elements.
/// - Parameter size: The maximum number of elements to store.
/// - Parameter prefetch: The strategy for initially populating the buffer.
/// - Parameter whenFull: The action to take when the buffer becomes full.
public init(upstream: Upstream,
size: Int,
prefetch: Publishers.PrefetchStrategy,
whenFull: Publishers.BufferingStrategy<Failure>) {
self.upstream = upstream
self.size = size
self.prefetch = prefetch
self.whenFull = whenFull
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
upstream.subscribe(Inner(downstream: subscriber, buffer: self))
}
}
}
extension Publishers.PrefetchStrategy: Equatable {}
extension Publishers.PrefetchStrategy: Hashable {}
extension Publishers.Buffer {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private enum State {
case ready(Publishers.Buffer<Upstream>, Downstream)
case subscribed(Publishers.Buffer<Upstream>, Downstream, Subscription)
case terminal
}
private let lock = UnfairLock.allocate()
private var recursion = false
private var state: State
private var downstreamDemand = Subscribers.Demand.none
// TODO: Use a deque here?
// Need to measure performance with large buffers and `dropOldest` strategy.
private var values = [Input]()
private var upstreamFailed = false
private var terminal: Subscribers.Completion<Failure>?
init(downstream: Downstream, buffer: Publishers.Buffer<Upstream>) {
state = .ready(buffer, downstream)
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case let .ready(buffer, downstream) = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(buffer, downstream, subscription)
lock.unlock()
let upstreamDemand: Subscribers.Demand
switch buffer.prefetch {
case .keepFull:
upstreamDemand = .max(buffer.size)
case .byRequest:
upstreamDemand = .unlimited
}
subscription.request(upstreamDemand)
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(buffer, _, subscription) = state else {
lock.unlock()
return .none
}
switch terminal {
case nil, .finished?:
if values.count >= buffer.size {
switch buffer.whenFull {
case .dropNewest:
lock.unlock()
return drain()
case .dropOldest:
values.removeFirst()
case let .customError(makeError):
terminal = .failure(makeError())
lock.unlock()
subscription.cancel()
return .none
}
}
values.append(input)
lock.unlock()
return drain()
case .failure?:
lock.unlock()
return .none
}
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case .subscribed = state, terminal == nil else {
lock.unlock()
return
}
terminal = completion
lock.unlock()
_ = drain()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
downstreamDemand += demand
let recursion = self.recursion
lock.unlock()
if recursion {
return
}
// Request the number of items just enough to fill the buffer.
subscription.request(drain() + demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(_, _, subscription) = state else {
lock.unlock()
return
}
state = .terminal
values = []
lock.unlock()
subscription.cancel()
}
private func drain() -> Subscribers.Demand {
var upstreamDemand = Subscribers.Demand.none
lock.lock()
while true {
guard case let .subscribed(buffer, downstream, _) = state else {
lock.unlock()
return upstreamDemand
}
if downstreamDemand > 0 {
if values.isEmpty {
if let completion = terminal {
state = .terminal
lock.unlock()
downstream.receive(completion: completion)
} else {
lock.unlock()
}
return upstreamDemand
}
} else {
if let completion = terminal, case .failure = completion {
state = .terminal
lock.unlock()
downstream.receive(completion: completion)
} else {
lock.unlock()
}
return upstreamDemand
}
let poppedValues = lockedPop(downstreamDemand)
assert(poppedValues.count > 0,
"""
We check that the buffer is not empty and downstreamDemand is \
nonzero, how can this be triggered?
""")
// This should not crash because `lockedPop(_:)` returns at most
// `downstreamDemand` items.
downstreamDemand -= poppedValues.count
recursion = true
lock.unlock()
var newDownstreamDemand = Subscribers.Demand.none
var additionalUpstreamDemand = 0
for value in poppedValues {
newDownstreamDemand += downstream.receive(value)
additionalUpstreamDemand += 1
}
if buffer.prefetch == .keepFull {
upstreamDemand += additionalUpstreamDemand
}
lock.lock()
recursion = false
downstreamDemand += newDownstreamDemand
}
}
private func lockedPop(_ demand: Subscribers.Demand) -> [Input] {
assert(demand > 0)
guard let max = demand.max else {
let poppedValues = self.values
self.values = []
return poppedValues
}
let poppedValues = Array(values.prefix(max))
values.removeFirst(poppedValues.count)
return poppedValues
}
var description: String { return "Buffer" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("values", values),
("state", state),
("downstreamDemand", downstreamDemand),
("terminal", terminal as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,401 @@
${template_header}
//
// Publishers.Catch.swift
//
//
// Created by Sergej Jaskiewicz on 25.12.2019.
//
%{
instantiations = ['Catch', 'TryCatch']
}%
extension Publisher {
/// Handles errors from an upstream publisher by replacing it with another publisher.
///
/// The following example replaces any error from the upstream publisher and replaces
/// the upstream with a `Just` publisher. This continues the stream by publishing
/// a single value and completing normally.
/// ```
/// enum SimpleError: Error { case error }
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
/// if v < 5 {
/// return v
/// } else {
/// throw SimpleError.error
/// }
/// }
///
/// let noErrorPublisher = errorPublisher.catch { _ in
/// return Just(100)
/// }
/// ```
/// Backpressure note: This publisher passes through `request` and `cancel` to
/// the upstream. After receiving an error, the publisher sends sends any unfulfilled
/// demand to the new `Publisher`.
///
/// - Parameter handler: A closure that accepts the upstream failure as input and
/// returns a publisher to replace the upstream publisher.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher.
public func `catch`<NewPublisher: Publisher>(
_ handler: @escaping (Failure) -> NewPublisher
) -> Publishers.Catch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
/// Handles errors from an upstream publisher by either replacing it with another
/// publisher or `throw`ing a new error.
///
/// - Parameter handler: A `throw`ing closure that accepts the upstream failure as
/// input and returns a publisher to replace the upstream publisher or if an error
/// is thrown will send the error downstream.
/// - Returns: A publisher that handles errors from an upstream publisher by replacing
/// the failed publisher with another publisher.
public func tryCatch<NewPublisher: Publisher>(
_ handler: @escaping (Failure) throws -> NewPublisher
) -> Publishers.TryCatch<Self, NewPublisher>
where NewPublisher.Output == Output
{
return .init(upstream: self, handler: handler)
}
}
extension Publishers {
/// A publisher that handles errors from an upstream publisher by replacing the failed
/// publisher with another publisher.
public struct Catch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = NewPublisher.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A closure that accepts the upstream failure as input and returns a publisher
/// to replace the upstream publisher.
public let handler: (Upstream.Failure) -> NewPublisher
/// Creates a publisher that handles errors from an upstream publisher by
/// replacing the failed publisher with another publisher.
///
/// - Parameters:
/// - upstream: The publisher that this publisher receives elements from.
/// - handler: A closure that accepts the upstream failure as input and returns
/// a publisher to replace the upstream publisher.
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
/// A publisher that handles errors from an upstream publisher by replacing the failed
/// publisher with another publisher or optionally producing a new error.
public struct TryCatch<Upstream: Publisher, NewPublisher: Publisher>: Publisher
where Upstream.Output == NewPublisher.Output
{
public typealias Output = Upstream.Output
public typealias Failure = Error
public let upstream: Upstream
public let handler: (Upstream.Failure) throws -> NewPublisher
public init(upstream: Upstream,
handler: @escaping (Upstream.Failure) throws -> NewPublisher) {
self.upstream = upstream
self.handler = handler
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, handler: handler)
let uncaughtS = Inner.UncaughtS(inner: inner)
upstream.subscribe(uncaughtS)
}
}
}
% for instantiation in instantiations:
% throws_modifier = ' throws' if instantiation == 'TryCatch' else ''
extension Publishers.${instantiation} {
private final class Inner<Downstream: Subscriber>
: Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output,
% if instantiation == 'Catch':
Downstream.Failure == NewPublisher.Failure
% else:
Downstream.Failure == Error
% end
{
struct UncaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePre(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePre(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
return inner.receivePre(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
struct CaughtS: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
typealias Input = NewPublisher.Output
typealias Failure = NewPublisher.Failure
let inner: Inner
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
func receive(subscription: Subscription) {
inner.receivePost(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return inner.receivePost(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
inner.receivePost(completion: completion)
}
var description: String { return inner.description }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
}
private enum State {
case pendingPre
case pre(Subscription)
case pendingPost
case post(Subscription)
case cancelled
}
private let lock = UnfairLock.allocate()
private var demand = Subscribers.Demand.none
private var state = State.pendingPre
private let downstream: Downstream
private let handler: (Upstream.Failure)${throws_modifier} -> NewPublisher
init(downstream: Downstream,
handler: @escaping (Upstream.Failure)${throws_modifier} -> NewPublisher) {
self.downstream = downstream
self.handler = handler
}
deinit {
lock.deallocate()
}
func receivePre(subscription: Subscription) {
lock.lock()
guard case .pendingPre = state else {
lock.unlock()
subscription.cancel()
return
}
state = .pre(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receivePre(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
demand -= 1
lock.unlock()
let newDemand = downstream.receive(input)
lock.lock()
demand += newDemand
lock.unlock()
return newDemand
}
func receivePre(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
lock.lock()
switch state {
case .pre:
state = .cancelled
lock.unlock()
downstream.receive(completion: .finished)
case .pendingPre, .pendingPost, .post, .cancelled:
lock.unlock()
}
case .failure(let error):
lock.lock()
switch state {
case .pre:
state = .pendingPost
lock.unlock()
% if instantiation == 'Catch':
handler(error).subscribe(CaughtS(inner: self))
% else:
do {
try handler(error).subscribe(CaughtS(inner: self))
} catch let anotherError {
lock.lock()
state = .cancelled
lock.unlock()
downstream.receive(completion: .failure(anotherError))
}
% end
case .cancelled:
lock.unlock()
case .pendingPre, .post, .pendingPost:
completionBeforeSubscription()
}
}
}
func receivePost(subscription: Subscription) {
lock.lock()
guard case .pendingPost = state else {
lock.unlock()
subscription.cancel()
return
}
state = .post(subscription)
let demand = self.demand
lock.unlock()
if demand > 0 {
subscription.request(demand)
}
}
func receivePost(_ input: NewPublisher.Output) -> Subscribers.Demand {
return downstream.receive(input)
}
func receivePost(completion: Subscribers.Completion<NewPublisher.Failure>) {
lock.lock()
guard case .post = state else {
lock.unlock()
return
}
state = .cancelled
lock.unlock()
% if instantiation == 'Catch':
downstream.receive(completion: completion)
% else:
downstream.receive(completion: completion.eraseError())
% end
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
switch state {
case .pendingPre:
// The client is only able to call the `request` method after we've sent
// `self` downstream. We only do it in the `receivePre(subscription:)`
// method, after setting `state` to `pre`.
// After that `state` never becomes `pendingPre`.
requestBeforeSubscription()
case let .pre(subscription):
self.demand += demand
lock.unlock()
subscription.request(demand)
case .pendingPost:
self.demand += demand
lock.unlock()
case let .post(subscription):
lock.unlock()
subscription.request(demand)
case .cancelled:
lock.unlock()
}
}
func cancel() {
lock.lock()
switch state {
case let .pre(subscription), let .post(subscription):
state = .cancelled
lock.unlock()
subscription.cancel()
case .pendingPre, .pendingPost, .cancelled:
lock.unlock()
}
}
var description: String { return "${instantiation}" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("demand", demand)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
% end
private func completionBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: received completion but do not have subscription",
file: file,
line: line)
}
private func requestBeforeSubscription(file: StaticString = #file,
line: UInt = #line) -> Never {
fatalError("Unexpected state: request before subscription sent",
file: file,
line: line)
}
@@ -0,0 +1,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,185 @@
//
// Publishers.CollectByCount.swift
//
//
// Created by Sergej Jaskiewicz on 24.12.2019.
//
extension Publisher {
/// Collects up to the specified number of elements, and then emits a single array of
/// the collection.
///
/// If the upstream publisher finishes before filling the buffer, this publisher sends
/// an array of all the items it has received. This may be fewer than `count`
/// elements.
/// If the upstream publisher fails with an error, this publisher forwards the error
/// to the downstream receiver instead of sending its output.
/// Note: When this publisher receives a request for `.max(n)` elements, it requests
/// `.max(count * n)` from the upstream publisher.
///
/// - Parameter count: The maximum number of received elements to buffer before
/// publishing.
/// - Returns: A publisher that collects up to the specified number of elements, and
/// then publishes them as an array.
public func collect(_ count: Int) -> Publishers.CollectByCount<Self> {
return .init(upstream: self, count: count)
}
}
extension Publishers {
/// A publisher that buffers a maximum number of items.
public struct CollectByCount<Upstream: Publisher>: Publisher {
public typealias Output = [Upstream.Output]
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The maximum number of received elements to buffer before publishing.
public let count: Int
public init(upstream: Upstream, count: Int) {
self.upstream = upstream
self.count = count
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
upstream.subscribe(Inner(downstream: subscriber, count: count))
}
}
}
extension Publishers.CollectByCount: Equatable where Upstream: Equatable {}
extension Publishers.CollectByCount {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == [Upstream.Output],
Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private let count: Int
private var buffer: [Input] = []
private var subscription: Subscription?
private var finished = false
private let lock = UnfairLock.allocate()
init(downstream: Downstream, count: Int) {
self.downstream = downstream
self.count = count
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
if finished || self.subscription != nil {
lock.unlock()
subscription.cancel()
return
}
self.subscription = subscription
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
if subscription == nil {
lock.unlock()
return .none
}
buffer.append(input)
guard buffer.count == count else {
lock.unlock()
return .none
}
let output = self.buffer
self.buffer = []
lock.unlock()
return downstream.receive(output) * count
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
subscription = nil
finished = true
switch completion {
case .finished:
if buffer.isEmpty {
lock.unlock()
} else {
let buffer = self.buffer
self.buffer = []
lock.unlock()
_ = downstream.receive(buffer)
}
case .failure:
buffer = []
lock.unlock()
}
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
if let subscription = self.subscription {
lock.unlock()
subscription.request(demand * count)
} else {
lock.unlock()
}
}
func cancel() {
lock.lock()
if let subscription = self.subscription {
buffer = []
finished = true
self.subscription = nil
lock.unlock()
subscription.cancel()
} else {
lock.unlock()
}
}
var description: String { return "CollectByCount" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
let children: [Mirror.Child] = [
("downstream", downstream),
("upstreamSubscription", subscription as Any),
("buffer", buffer),
("count", count)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,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,248 @@
//
// 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 another
/// publisher.
public struct Concatenate<Prefix: Publisher, Suffix: Publisher>: Publisher
where Prefix.Failure == Suffix.Failure, Prefix.Output == Suffix.Output
{
public typealias Output = Suffix.Output
public typealias Failure = Suffix.Failure
/// The publisher to republish, in its entirety, before republishing elements from
/// `suffix`.
public let prefix: Prefix
/// The publisher to republish only after `prefix` finishes.
public let suffix: Suffix
public init(prefix: Prefix, suffix: Suffix) {
self.prefix = prefix
self.suffix = suffix
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Suffix.Failure == Downstream.Failure, Suffix.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, suffix: suffix)
subscriber.receive(subscription: inner)
prefix.subscribe(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()
private let downstreamLock = UnfairRecursiveLock.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" }
}
}
@@ -5,6 +5,17 @@
// 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
@@ -33,62 +44,34 @@ extension Publishers {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Upstream.Failure == SubscriberType.Failure,
SubscriberType.Input == Output
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Downstream.Input == Output
{
let count = _Count<Upstream, SubscriberType>(downstream: subscriber)
upstream.subscribe(count)
upstream.subscribe(Inner(downstream: subscriber))
}
}
}
extension Publisher {
extension Publishers.Count: Equatable where Upstream: Equatable {}
/// Publishes the number of elements received from the upstream publisher.
///
/// - Returns: A publisher that consumes all elements until the upstream publisher
/// finishes, then emits a single value with the total number of elements received.
public func count() -> Publishers.Count<Self> {
return Publishers.Count(upstream: self)
}
}
private final class _Count<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Downstream.Input == Int,
Upstream.Failure == Downstream.Failure
{
typealias Input = Upstream.Output
typealias Output = Int
typealias Failure = Downstream.Failure
private var _count = 0
var description: String { return "Count" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
upstreamSubscription?.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
_count += 1
return .none
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if case .finished = completion {
_ = downstream.receive(_count)
extension Publishers.Count {
private final class Inner<Downstream: Subscriber>
: ReduceProducer<Downstream, Upstream.Output, Int, Failure, Void>
where Downstream.Input == Int,
Upstream.Failure == Downstream.Failure
{
fileprivate init(downstream: Downstream) {
super.init(downstream: downstream, initial: 0, reduce: ())
}
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
override func receive(
newValue: Upstream.Output
) -> PartialCompletion<Void, Downstream.Failure> {
result! += 1
return .continue
}
override var description: String { return "Count" }
}
}
@@ -0,0 +1,264 @@
//
// Publishers.Debounce.swift
//
//
// Created by Sergej Jaskiewicz on 17.12.2019.
//
extension Publisher {
/// Publishes elements only after a specified time interval elapses between events.
///
/// Use this operator when you want to wait for a pause in the delivery of events from
/// the upstream publisher. For example, call `debounce` on the publisher from a text
/// field to only receive elements when the user pauses or stops typing. When they
/// start typing again, the `debounce` holds event delivery until the next pause.
///
/// - Parameters:
/// - dueTime: The time the publisher should wait before publishing an element.
/// - scheduler: The scheduler on which this publisher delivers elements
/// - options: Scheduler options that customize this publishers delivery
/// of elements.
/// - Returns: A publisher that publishes events only after a specified time elapses.
public func debounce<Context: Scheduler>(
for dueTime: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions? = nil
) -> Publishers.Debounce<Self, Context> {
return .init(upstream: self,
dueTime: dueTime,
scheduler: scheduler,
options: options)
}
}
extension Publishers {
/// A publisher that publishes elements only after a specified time interval elapses
/// between events.
public struct Debounce<Upstream: Publisher, Context: Scheduler>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The amount of time the publisher should wait before publishing an element.
public let dueTime: Context.SchedulerTimeType.Stride
/// The scheduler on which this publisher delivers elements.
public let scheduler: Context
/// Scheduler options that customize this publishers delivery of elements.
public let options: Context.SchedulerOptions?
public init(upstream: Upstream,
dueTime: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.upstream = upstream
self.dueTime = dueTime
self.scheduler = scheduler
self.options = options
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
let inner = Inner(downstream: subscriber,
dueTime: dueTime,
scheduler: scheduler,
options: options)
upstream.subscribe(inner)
}
}
}
extension Publishers.Debounce {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private typealias Generation = UInt64
private let lock = UnfairLock.allocate()
private let downstreamLock = UnfairRecursiveLock.allocate()
private let downstream: Downstream
private let dueTime: Context.SchedulerTimeType.Stride
private let scheduler: Context
private let options: Context.SchedulerOptions?
private var state = SubscriptionStatus.awaitingSubscription
private var currentCanceller: Cancellable?
private var currentValue: Output?
private var currentGeneration: Generation = 0
private var downstreamDemand = Subscribers.Demand.none
init(downstream: Downstream,
dueTime: Context.SchedulerTimeType.Stride,
scheduler: Context,
options: Context.SchedulerOptions?) {
self.downstream = downstream
self.dueTime = dueTime
self.scheduler = scheduler
self.options = options
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = state else {
lock.unlock()
subscription.cancel()
return
}
state = .subscribed(subscription)
lock.unlock()
downstreamLock.lock()
downstream.receive(subscription: self)
downstreamLock.unlock()
subscription.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
precondition(!state.isAwaitingSubscription)
guard case .subscribed = state else {
lock.unlock()
return .none
}
currentGeneration += 1
let generation = currentGeneration
currentValue = input
let due = scheduler.now.advanced(by: dueTime)
lock.unlock()
let newCanceller = scheduler.schedule(after: due,
interval: dueTime,
tolerance: scheduler.minimumTolerance,
options: options) { [weak self] in
self?.due(generation: generation)
}
lock.lock()
let canceller = currentCanceller
currentCanceller = newCanceller
lock.unlock()
canceller?.cancel()
return .none
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
precondition(!state.isAwaitingSubscription)
guard case .subscribed = state else {
lock.unlock()
return
}
state = .terminal
let canceller = currentCanceller
lock.unlock()
canceller?.cancel()
scheduler.schedule {
self.downstreamLock.lock()
self.downstream.receive(completion: completion)
self.downstreamLock.unlock()
}
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
precondition(!state.isAwaitingSubscription)
guard case .subscribed = state else {
lock.unlock()
return
}
downstreamDemand += demand
lock.unlock()
}
func cancel() {
lock.lock()
guard case .subscribed(let subscription) = state else {
lock.unlock()
return
}
state = .terminal
lock.unlock()
subscription.cancel()
}
var description: String { return "Debounce" }
var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream),
("downstreamDemand", downstreamDemand),
("currentValue", currentValue as Any)
]
return Mirror(self, children: children)
}
var playgroundDescription: Any { return description }
private func due(generation: Generation) {
lock.lock()
guard case .subscribed = state else {
lock.unlock()
return
}
// If this condition holds, it means that no values were received
// in this time frame => we should propagate the current value downstream.
guard generation == currentGeneration, let value = currentValue else {
let canceller = currentCanceller
lock.unlock()
canceller?.cancel()
return
}
let hasAnyDemand = downstreamDemand > 0
if hasAnyDemand {
downstreamDemand -= 1
}
let canceller = currentCanceller!
lock.unlock()
canceller.cancel()
guard hasAnyDemand else { return }
downstreamLock.lock()
let newDemand = downstream.receive(value)
downstreamLock.unlock()
if newDemand == .none { return }
lock.lock()
downstreamDemand += newDemand
lock.unlock()
}
}
}
@@ -1,109 +0,0 @@
//
// Publishers.Decode.swift
//
//
// Created by Joseph Spadafora on 6/21/19.
//
extension Publishers {
public struct Decode<Upstream, Output, Coder>: Publisher
where Upstream: Publisher,
Output: Decodable,
Coder: TopLevelDecoder,
Upstream.Output == Coder.Input
{
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
public let upstream: Upstream
private let _decoder: Coder
public init(upstream: Upstream, decoder: Coder) {
self.upstream = upstream
self._decoder = decoder
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
let decodeSubscriber = _Decode<Upstream, SubscriberType, Coder>(
downstream: subscriber,
decoder: _decoder
)
upstream.subscribe(decodeSubscriber)
}
}
}
private final class _Decode<Upstream: Publisher,
Downstream: Subscriber,
Coder: TopLevelDecoder>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Downstream.Input: Decodable,
Coder.Input == Upstream.Output,
Downstream.Failure == Error {
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
private let _decoder: Coder
var description: String { return "Decode" }
init(downstream: Downstream, decoder: Coder) {
self._decoder = decoder
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
let value = try _decoder.decode(Downstream.Input.self, from: input)
return downstream.receive(value)
} catch {
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
}
extension Publisher {
public func decode<Item: Decodable,
Coder: TopLevelDecoder>(
type: Item.Type,
decoder: Coder
) -> Publishers.Decode<Self, Item, Coder>
where Self.Output == Coder.Input
{
return Publishers.Decode(upstream: self, decoder: decoder)
}
}
@@ -0,0 +1,207 @@
//
// Publishers.Delay.swift
// OpenCombine
//
// Created by Евгений Богомолов on 07/09/2019.
//
extension Publisher {
/// Delays delivery of all output to the downstream receiver by a specified amount
/// of time on a particular scheduler.
///
/// 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 = UnfairRecursiveLock.allocate()
fileprivate init(_ publisher: Delay, downstream: Downstream) {
state = .ready(publisher, downstream)
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
private func schedule(_ delay: Delay, work: @escaping () -> Void) {
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()
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) {
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) {
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,143 @@
//
// Publishers.Drop.swift
//
//
// Created by Sven Weidauer on 03.10.2019.
//
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 }
}
}
@@ -0,0 +1,267 @@
//
// Publishers.DropUntilOutput.swift
//
//
// Created by Sergej Jaskiewicz on 24.12.2019.
//
extension Publisher {
/// Ignores elements from the upstream publisher until it receives an element from
/// a second publisher.
///
/// This publisher requests a single value from the upstream publisher, and it ignores
/// (drops) all elements from that publisher until the upstream publisher produces
/// a value. After the `other` publisher produces an element, this publisher cancels
/// its subscription to the `other` publisher, and allows events from the `upstream`
/// publisher to pass through.
/// After this publisher receives a subscription from the upstream publisher, it
/// passes through backpressure requests from downstream to the upstream publisher.
/// If the upstream publisher acts on those requests before the other publisher
/// produces an item, this publisher drops the elements it receives from the upstream
/// publisher.
///
/// - Parameter publisher: A publisher to monitor for its first emitted element.
/// - Returns: A publisher that drops elements from the upstream publisher until the
/// `other` publisher produces a value.
public func drop<Other: Publisher>(
untilOutputFrom publisher: Other
) -> Publishers.DropUntilOutput<Self, Other> where Failure == Other.Failure {
return .init(upstream: self, other: publisher)
}
}
extension Publishers {
/// A publisher that ignores elements from the upstream publisher until it receives
/// an element from second publisher.
public struct DropUntilOutput<Upstream: Publisher, Other: Publisher>: Publisher
where Upstream.Failure == Other.Failure
{
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher that this publisher receives elements from.
public let upstream: Upstream
/// A publisher to monitor for its first emitted element.
public let other: Other
/// Creates a publisher that ignores elements from the upstream publisher until
/// it receives an element from another publisher.
///
/// - Parameters:
/// - upstream: A publisher to drop elements from while waiting for another
/// publisher to emit elements.
/// - other: A publisher to monitor for its first emitted element.
public init(upstream: Upstream, other: Other) {
self.upstream = upstream
self.other = other
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input,
Other.Failure == Downstream.Failure
{
let inner = Inner(downstream: subscriber)
subscriber.receive(subscription: inner)
other.subscribe(Inner.OtherSubscriber(inner: inner))
upstream.subscribe(inner)
}
}
}
extension Publishers.DropUntilOutput: Equatable
where Upstream: Equatable, Other: Equatable {}
extension Publishers.DropUntilOutput {
fileprivate final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let downstream: Downstream
private var triggered = false
private let lock = UnfairLock.allocate()
private let downstreamLock = UnfairRecursiveLock.allocate()
private var upstreamSubscription: Subscription?
private var pendingDemand = Subscribers.Demand.none
private var otherSubscription: Subscription?
private var otherFinished = false
private var cancelled = false
init(downstream: Downstream) {
self.downstream = downstream
}
deinit {
lock.deallocate()
downstreamLock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard upstreamSubscription == nil && !cancelled else {
lock.unlock()
subscription.cancel()
return
}
upstreamSubscription = subscription
if pendingDemand > 0 {
lock.unlock()
subscription.request(pendingDemand)
} else {
lock.unlock()
}
}
func receive(_ input: Input) -> Subscribers.Demand {
lock.lock()
if !triggered || cancelled {
pendingDemand -= 1
lock.unlock()
return .none
}
lock.unlock()
downstreamLock.lock()
let newDemand = downstream.receive(input)
downstreamLock.unlock()
return newDemand
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
if cancelled {
lock.unlock()
return
}
cancelled = true
lock.unlock()
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
private func receiveOther(subscription: Subscription) {
// Combine doesn't lock here
guard otherSubscription == nil else {
subscription.cancel()
return
}
otherSubscription = subscription
subscription.request(.max(1))
}
private func receiveOther(_ input: Other.Output) -> Subscribers.Demand {
lock.lock()
triggered = true
otherSubscription = nil
lock.unlock()
return .none
}
private func receiveOther(completion: Subscribers.Completion<Other.Failure>) {
lock.lock()
if triggered {
otherSubscription = nil
lock.unlock()
return
}
otherFinished = true
if let upstreamSubscription = self.upstreamSubscription {
self.upstreamSubscription = nil
lock.unlock()
upstreamSubscription.cancel()
} else {
lock.unlock()
}
downstreamLock.lock()
downstream.receive(completion: completion)
downstreamLock.unlock()
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
pendingDemand += demand
if let subscription = upstreamSubscription {
lock.unlock()
subscription.request(demand)
} else {
lock.unlock()
}
}
func cancel() {
lock.lock()
let upstreamSubscription = self.upstreamSubscription
let otherSubscription = self.otherSubscription
self.upstreamSubscription = nil
self.otherSubscription = nil
cancelled = true
lock.unlock()
upstreamSubscription?.cancel()
otherSubscription?.cancel()
}
var description: String { return "DropUntilOutput" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
extension Publishers.DropUntilOutput.Inner {
fileprivate struct OtherSubscriber
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
let inner: Publishers.DropUntilOutput<Upstream, Other>.Inner<Downstream>
var combineIdentifier: CombineIdentifier {
return inner.combineIdentifier
}
func receive(subscription: Subscription) {
inner.receiveOther(subscription: subscription)
}
func receive(_ input: Other.Output) -> Subscribers.Demand {
return inner.receiveOther(input)
}
func receive(completion: Subscribers.Completion<Other.Failure>) {
inner.receiveOther(completion: completion)
}
var description: String { return "DropUntilOutput" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -5,6 +5,40 @@
// Created by Sergej Jaskiewicz on 16.06.2019.
//
extension Publisher {
/// Omits elements from the upstream publisher until a given closure returns false,
/// before republishing all remaining elements.
///
/// - 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 +60,10 @@ extension Publishers {
self.predicate = predicate
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.subscribe(inner)
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
@@ -53,151 +86,252 @@ extension Publishers {
self.predicate = predicate
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, SubscriberType.Failure == Error
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Error
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.subscribe(inner)
upstream.subscribe(Inner(downstream: subscriber, predicate: predicate))
}
}
}
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscription
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Predicate = (Input) -> Result<Bool, Downstream.Failure>
/// The predicate is reset to `nil` as soon as it returns `false`.
var predicate: Predicate?
var isCompleted = false
init(downstream: Downstream, predicate: @escaping Predicate) {
self.predicate = predicate
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard upstreamSubscription != nil else {
return .none
}
guard let predicate = self.predicate else {
return downstream.receive(input)
}
// NOTE: until the predicate returns false, we will ask the upstream publisher
// for elements one by one.
//
// However, IF the downstream requests anything, we accumulate this demand in the
// `demand` property so that later we can provide the downstream with the correct
// amount of values.
//
// As soon as the predicate returns false, we switch to the mode where
// we just forward all the requests from the downstream to the upstream.
switch predicate(input) {
case .success(true):
return .max(1)
case .success(false):
// Okay, we hit the first element not satisfying the predicate,
// from now on we just republish the values to the downstream.
self.predicate = nil
return downstream.receive(input)
case .failure(let error):
downstream.receive(completion: .failure(error))
isCompleted = true
cancel()
return .none
}
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
override func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
// Don't zero out downstream, that's what Combine does (probably a bug)
}
}
extension Publishers.DropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
: Subscriber,
Subscription,
CustomStringConvertible,
Subscriber
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
var description: String { return "DropWhile" }
// NOTE: This class has been audited for thread safety.
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else {
assertionFailure("unreachable")
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,
Subscription,
CustomStringConvertible,
Subscriber
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
var description: String { return "TryDropWhile" }
// NOTE: This class has been audited for thread safety.
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private var status = SubscriptionStatus.awaitingSubscription
private let downstream: Downstream
private var predicate: ((Input) throws -> Bool)?
private var dropping = true
private var finished = false
private let lock = UnfairLock.allocate()
fileprivate init(downstream: Downstream,
predicate: @escaping (Input) throws -> Bool) {
self.downstream = downstream
self.predicate = predicate
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case let .subscribed(subscription) = status,
let shouldDrop = predicate else {
lock.unlock()
return .none
}
let dropping = self.dropping
lock.unlock()
if dropping {
do {
if try shouldDrop(input) {
return .max(1)
} else {
lock.lock()
self.dropping = false
lock.unlock()
}
} catch {
lock.lock()
status = .terminal
predicate = nil
finished = true
lock.unlock()
subscription.cancel()
downstream.receive(completion: .failure(error))
return .none
}
}
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
downstream.receive(completion: completion.eraseError())
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
let wasFinished = finished
finished = true
lock.unlock()
if !wasFinished {
downstream.receive(completion: completion.eraseError())
}
}
}
}
extension Publisher {
/// Omits elements from the upstream publisher until a given closure returns false,
/// before republishing all remaining elements.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the publishers
/// output.
/// - Returns: A publisher that skips over elements until the provided closure returns
/// `false`.
public func drop(
while predicate: @escaping (Output) -> Bool
) -> Publishers.DropWhile<Self> {
return Publishers.DropWhile(upstream: self, predicate: predicate)
}
/// Omits elements from the upstream publisher until an error-throwing closure returns
/// false, before republishing all remaining elements.
///
/// If the predicate closure throws, the publisher fails with an error.
///
/// - Parameter predicate: A closure that takes an element as a parameter and returns
/// a Boolean value indicating whether to drop the element from the publishers
/// output.
/// - Returns: A publisher that skips over elements until the provided closure returns
/// `false`, and then republishes all remaining elements. If the predicate closure
/// throws, the publisher fails with an error.
public func tryDrop(
while predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryDropWhile<Self> {
return Publishers.TryDropWhile(upstream: self, predicate: predicate)
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
lock.unlock()
subscription.request(demand)
}
func cancel() {
lock.lock()
guard case let .subscribed(subscription) = status else {
lock.unlock()
return
}
status = .terminal
predicate = nil
finished = true
lock.unlock()
subscription.cancel()
}
var description: String { return "TryDropWhile" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
var playgroundDescription: Any { return description }
}
}
@@ -1,109 +0,0 @@
//
// Publishers.Encode.swift
//
//
// Created by Joseph Spadafora on 6/22/19.
//
extension Publishers {
public struct Encode<Upstream, Coder>: Publisher
where Upstream: Publisher,
Coder: TopLevelEncoder,
Upstream.Output: Encodable
{
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Error
/// The kind of values published by this publisher.
public typealias Output = Coder.Output
public let upstream: Upstream
private let encoder: Coder
public init(upstream: Upstream, encoder: Coder) {
self.upstream = upstream
self.encoder = encoder
}
/// This function is called to attach the specified `Subscriber`
/// to this `Publisher` by `subscribe(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
let encodeSubscriber = _Encode<Upstream, SubscriberType, Coder>(
downstream: subscriber,
encoder: encoder
)
upstream.subscribe(encodeSubscriber)
}
}
}
private final class _Encode<Upstream: Publisher,
Downstream: Subscriber,
Coder: TopLevelEncoder>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Coder.Output == Downstream.Input,
Upstream.Output: Encodable,
Downstream.Failure == Error {
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
private let _encoder: Coder
var description: String { return "Encode" }
init(downstream: Downstream, encoder: Coder) {
self._encoder = encoder
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
do {
let value = try _encoder.encode(input)
return downstream.receive(value)
} catch {
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion.eraseError())
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
}
extension Publisher {
public func encode<Coder>(
encoder: Coder
) -> Publishers.Encode<Self, Coder>
where Coder: TopLevelEncoder
{
return Publishers.Encode(upstream: self, encoder: encoder)
}
}
@@ -0,0 +1,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
@@ -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 satisfies the predicate.
public func first(
where predicate: @escaping (Output) -> Bool
) -> Publishers.FirstWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes the first element of a stream to satisfy a
/// throwing predicate closure, then finishes.
///
/// 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 satisfies 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,424 @@
//
// Publishers.FlatMap.swift
//
// Created by Eric Patey on 16.08.2019.
//
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?
/// The lock for requesting from `outerSubscription`.
private let outerLock = UnfairRecursiveLock.allocate()
/// The lock for modifying the state. All mutable state here should be
/// read and modified with this lock acquired.
/// The only exception is the `downstreamRecursive` field, which is guarded
/// by the `downstreamLock`.
private let lock = UnfairLock.allocate()
/// All the calls to the downstream subscriber should be made with this lock
/// acquired.
private let downstreamLock = UnfairRecursiveLock.allocate()
private let downstream: Downstream
private var downstreamDemand = Subscribers.Demand.none
/// This variable is set to `true` whenever we call `downstream.receive(_:)`,
/// and then set back to `false`.
private var downstreamRecursive = false
private var innerRecursive = false
private var subscriptions = [SubscriptionIndex : Subscription]()
private var nextInnerIndex: SubscriptionIndex = 0
private var pendingSubscriptions = 0
private var buffer = [(SubscriptionIndex, Child.Output)]()
private let maxPublishers: Subscribers.Demand
private let 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,193 @@
//
// Publishers.HandleEvents.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
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,132 @@
//
// Publishers.IgnoreOutput.swift
//
// Created by Eric Patey on 16.08.2019.
//
extension Publisher {
/// Ignores 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()
}
}
}
@@ -30,7 +30,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 +39,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
@@ -54,6 +54,12 @@ extension Publishers {
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
@@ -78,12 +84,6 @@ extension Publishers {
}
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.subscribe(inner)
}
public func map<Result>(
_ transform: @escaping (Output) -> Result
@@ -103,8 +103,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.subscribe(inner)
upstream.subscribe(Inner(downstream: subscriber, map: transform))
}
public func map<Result>(
@@ -120,89 +119,158 @@ extension Publishers.TryMap {
}
}
private class _Map<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Transform = (Input) -> Result<Downstream.Input, Downstream.Failure>
fileprivate var _transform: Transform?
var isCompleted: Bool {
return _transform == nil
}
init(downstream: Downstream, transform: @escaping Transform) {
_transform = transform
super.init(downstream: downstream)
}
func receive(_ 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
}
}
}
extension Publishers.Map {
private final class Inner<Downstream: Subscriber>
: _Map<Upstream, Downstream>,
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
Subscriber
where Downstream.Failure == Upstream.Failure
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
{
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,
Subscription,
CustomStringConvertible,
Subscriber
where Downstream.Failure == Error
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Downstream.Input == Output, 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 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) {
upstreamSubscription = subscription
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
}
func receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_transform = nil
downstream.receive(completion: completion.eraseError())
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 request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
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())
}
override func cancel() {
_transform = nil
super.cancel()
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 }
}
}
@@ -32,15 +32,11 @@ extension Publishers {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure,
Upstream.Output == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let mapErrorSubscriber = _MapError<Upstream, SubscriberType>(
downstream: subscriber,
transform: transform
)
upstream.subscribe(mapErrorSubscriber)
upstream.subscribe(Inner(downstream: subscriber, map: transform))
}
}
}
@@ -57,48 +53,57 @@ extension Publisher {
/// - Returns: A publisher that replaces any upstream failure with a
/// new error produced by the `transform` closure.
public func mapError<NewFailure: Error>(
_ transform: @escaping (Self.Failure) -> NewFailure
_ transform: @escaping (Failure) -> NewFailure
) -> Publishers.MapError<Self, NewFailure>
{
return Publishers.MapError(upstream: self, transform)
}
}
private final class _MapError<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
extension Publishers.MapError {
private let _transform: (Upstream.Failure) -> Downstream.Failure
private struct Inner<Downstream: Subscriber>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
var description: String { return "MapError" }
private let downstream: Downstream
private let map: (Upstream.Failure) -> Downstream.Failure
init(downstream: Downstream,
transform: @escaping (Upstream.Failure) -> Downstream.Failure) {
self._transform = transform
super.init(downstream: downstream)
}
let combineIdentifier = CombineIdentifier()
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: subscription)
}
var description: String { return "MapError" }
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(input)
}
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure(let error):
downstream.receive(completion: .failure(_transform(error)))
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,166 @@
//
// Publishers.MeasureInterval.swift
//
//
// Created by Sergej Jaskiewicz on 03.12.2019.
//
extension Publisher {
/// Measures and emits the time interval between events received from an upstream
/// publisher.
///
/// 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 }
}
}
@@ -7,6 +7,15 @@
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>
@@ -15,6 +24,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>
@@ -26,43 +43,67 @@ extension Publisher {
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
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 lazy var _subject: SubjectType = self.createSubject()
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
{
_subject.subscribe(Inner(downstream: subscriber))
lazySubject.subscribe(Inner(parent: self, downstream: subscriber))
}
public func connect() -> Cancellable {
let subscriber = SubjectSubscriber(_subject)
upstream.subscribe(subscriber)
return AnyCancellable {
subscriber.downstreamSubject = nil
}
return upstream.subscribe(lazySubject)
}
}
}
@@ -70,32 +111,106 @@ extension Publishers {
extension Publishers.Multicast {
private final class Inner<Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
: Subscriber,
Subscription,
CustomStringConvertible,
Subscription
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
var description: String { return "Multicast" }
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) {
upstreamSubscription = 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 {
return downstream.receive(input)
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) {
upstreamSubscription?.request(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()
}
}
}
@@ -0,0 +1,205 @@
//
// Publishers.Output.swift
//
//
// Created by Sergej Jaskiewicz on 24.10.2019.
//
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,19 @@
// Created by Sergej Jaskiewicz on 16.06.2019.
//
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,8 +56,8 @@ 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.subscribe(inner)
@@ -52,112 +65,122 @@ extension Publishers {
}
}
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)
mutating func write(_ string: String) {
stream.write(string)
}
}
_downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
_logDemand(demand, synchronous: false)
_upstreamSubscription?.request(demand)
}
private var downstream: Downstream
private let prefix: String
private var stream: PrintTarget?
private var status = SubscriptionStatus.awaitingSubscription
private let lock = UnfairLock.allocate()
func cancel() {
_log("receive cancel")
_upstreamSubscription?.cancel()
_upstreamSubscription = nil
}
var description: String { return "Print" }
var customMirror: Mirror { return Mirror(self, children: EmptyCollection()) }
private func _log(_ description: String,
value: Any? = nil,
additionalInfo: String = "") {
_printerLock.do {
if !_prefix.isEmpty {
_stream.write(_prefix)
_stream.write(": ")
}
_stream.write(description)
if let value = value {
_stream.write(": (")
_stream.write(String(describing: value))
_stream.write(")")
}
if !additionalInfo.isEmpty {
_stream.write(" (")
_stream.write(additionalInfo)
_stream.write(")")
}
_stream.write("\n")
init(downstream: Downstream, prefix: String, stream: TextOutputStream?) {
self.downstream = downstream
self.prefix = prefix.isEmpty ? "" : "\(prefix): "
self.stream = stream.map(PrintTarget.init)
}
}
private func _logDemand(_ demand: Subscribers.Demand, synchronous: Bool) {
let synchronouslyStr = synchronous ? "synchronous" : ""
if let max = demand.max {
_log("request max", value: max, additionalInfo: synchronouslyStr)
} else {
_log("request unlimited", additionalInfo: synchronouslyStr)
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
log("\(prefix)receive subscription: (\(subscription))")
lock.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,201 @@
//
// Publishers.ReceiveOn.swift
//
//
// Created by Sergej Jaskiewicz on 02.12.2019.
//
extension Publisher {
/// Specifies the scheduler on which to receive elements from the publisher.
///
/// You use the `receive(on:options:)` operator to receive results 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 = UnfairRecursiveLock.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()
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) {
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) {
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 previous
/// 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,180 @@
//
// Publishers.ReplaceEmpty.swift
// OpenCombine
//
// Created by Joe Spadafora on 12/10/19.
//
extension Publisher {
/// Replaces an empty stream with the provided element.
///
/// If the upstream publisher finishes without producing any elements,
/// this publisher emits the provided element, then finishes normally.
/// - Parameter output: An element to emit when the upstream publisher
/// finishes without emitting any elements.
/// - Returns: A publisher that replaces an empty stream with
/// the provided output element.
public func replaceEmpty(with output: Output) -> Publishers.ReplaceEmpty<Self> {
return .init(upstream: self, output: output)
}
}
extension Publishers {
/// A publisher that replaces an empty stream with a provided element.
public struct ReplaceEmpty<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The element to deliver when the upstream publisher finishes
/// without delivering any elements.
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
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, output: output)
upstream.subscribe(inner)
}
}
}
extension Publishers.ReplaceEmpty: Equatable
where Upstream: Equatable, Upstream.Output: Equatable {}
extension Publishers.ReplaceEmpty {
private final class Inner<Downstream: Subscriber>
: Subscriber,
Subscription,
CustomStringConvertible,
CustomReflectable,
CustomPlaygroundDisplayConvertible
where Upstream.Failure == Downstream.Failure,
Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private let output: Output
private let downstream: Downstream
private var receivedUpstream = false
private var lock = UnfairLock.allocate()
private var downstreamRequested = false
private var finishedWithoutUpstream = false
private var status = SubscriptionStatus.awaitingSubscription
fileprivate init(downstream: Downstream, output: Output) {
self.downstream = downstream
self.output = output
}
deinit {
lock.deallocate()
}
func receive(subscription: Subscription) {
lock.lock()
guard case .awaitingSubscription = status else {
lock.unlock()
subscription.cancel()
return
}
status = .subscribed(subscription)
lock.unlock()
downstream.receive(subscription: self)
subscription.request(.unlimited)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return .none
}
receivedUpstream = true
lock.unlock()
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
lock.lock()
guard case .subscribed = status else {
lock.unlock()
return
}
status = .terminal
if receivedUpstream {
lock.unlock()
downstream.receive(completion: completion)
return
}
switch completion {
case .finished:
if downstreamRequested {
lock.unlock()
_ = downstream.receive(output)
downstream.receive(completion: completion)
return
}
finishedWithoutUpstream = true
lock.unlock()
case .failure:
lock.unlock()
downstream.receive(completion: completion)
}
}
func request(_ demand: Subscribers.Demand) {
demand.assertNonZero()
lock.lock()
downstreamRequested = true
if finishedWithoutUpstream {
lock.unlock()
_ = downstream.receive(output)
downstream.receive(completion: .finished)
return
}
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 "ReplaceEmpty" }
var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
}
var playgroundDescription: Any { return description }
}
}
@@ -0,0 +1,188 @@
//
// Publishers.ReplaceError.swift
// OpenCombine
//
// Created by Bogdan Vlad on 8/29/19.
//
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 }
}
}
@@ -7,7 +7,7 @@
extension Publisher {
/// Replaces nil elements in the stream with the proviced element.
/// Replaces nil elements in the stream with the provided element.
///
/// - Parameter output: The element to use when replacing `nil`.
/// - Returns: A publisher that replaces `nil` elements from
@@ -0,0 +1,296 @@
//
// Publishers.Scan.swift
//
// Created by Eric Patey on 26.08.2019.
//
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 }
}
}
@@ -25,11 +25,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,66 +46,93 @@ 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()
}
}
}
@@ -31,8 +31,7 @@ extension Publishers {
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
let inner = Inner(downstream: subscriber)
upstream.subscribe(inner)
upstream.subscribe(Inner(downstream: subscriber))
}
public func setFailureType<NewFailure: Error>(
@@ -63,12 +62,20 @@ extension Publisher where Failure == Never {
}
extension Publishers.SetFailureType {
private final class Inner<Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input
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)
}
@@ -82,5 +89,11 @@ extension Publishers.SetFailureType {
}
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 receives 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,188 @@
//
// Publishers.SubscribeOn.swift
//
//
// Created by Sergej Jaskiewicz on 02.12.2019.
//
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 }
}
}

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