From 294b9e0dfc4a806045db59bc7306c1d9b9a9a5f2 Mon Sep 17 00:00:00 2001 From: Ayal Spitz Date: Thu, 9 Jan 2020 02:06:12 -0500 Subject: [PATCH] Extended Sample operation to support a default value (#1457) --- .gitignore | 1 + CHANGELOG.md | 1 + RxSwift/Observables/Sample.swift | 24 ++++++---- Sources/AllTestz/main.swift | 1 + .../RxSwiftTests/Observable+SampleTests.swift | 46 +++++++++++++++++++ 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 4f56b6f54..4272d6861 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ Carthage/Build .build/ Packages/ +.swiftpm # AppCode diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fcf956d6..22e17567a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file. * Add `UITextField.isSecureTextEntry` binder. #1968 * Remove "custom" `Result` in favor of `Foundation.Resault`. #2006 * Fix compilation error in `SharedSequence.createUnsafe`. #2014 +* Added `defaultValue` to `sample` to be returned when no new events occur between sampler ticks. #1457 ## [5.1.0](https://github.com/ReactiveX/RxSwift/releases/tag/5.1.0) diff --git a/RxSwift/Observables/Sample.swift b/RxSwift/Observables/Sample.swift index a7d3bb381..9301c9af7 100644 --- a/RxSwift/Observables/Sample.swift +++ b/RxSwift/Observables/Sample.swift @@ -13,16 +13,18 @@ extension ObservableType { Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. - **In case there were no new elements between sampler ticks, no element is sent to the resulting sequence.** + **In case there were no new elements between sampler ticks, you may provide a default value to be emitted, instead + to the resulting sequence otherwise no element is sent.** - seealso: [sample operator on reactivex.io](http://reactivex.io/documentation/operators/sample.html) - parameter sampler: Sampling tick sequence. + - parameter defaultValue: a value to return if there are no new elements between sampler ticks - returns: Sampled observable sequence. */ - public func sample(_ sampler: Source) + public func sample(_ sampler: Source, defaultValue: Element? = nil) -> Observable { - return Sample(source: self.asObservable(), sampler: sampler.asObservable()) + return Sample(source: self.asObservable(), sampler: sampler.asObservable(), defaultValue: defaultValue) } } @@ -51,7 +53,7 @@ final private class SamplerSink func synchronized_on(_ event: Event) { switch event { case .next, .completed: - if let element = parent.element { + if let element = parent.element ?? self.parent.defaultValue { self.parent.element = nil self.parent.forwardOn(.next(element)) } @@ -75,7 +77,8 @@ final private class SampleSequenceSink typealias Element = Observer.Element typealias Parent = Sample - private let parent: Parent + fileprivate let parent: Parent + fileprivate let defaultValue: Element? let lock = RecursiveLock() @@ -85,8 +88,9 @@ final private class SampleSequenceSink private let sourceSubscription = SingleAssignmentDisposable() - init(parent: Parent, observer: Observer, cancel: Cancelable) { + init(parent: Parent, observer: Observer, cancel: Cancelable, defaultValue: Element? = nil) { self.parent = parent + self.defaultValue = defaultValue super.init(observer: observer, cancel: cancel) } @@ -119,14 +123,16 @@ final private class SampleSequenceSink final private class Sample: Producer { fileprivate let source: Observable fileprivate let sampler: Observable - - init(source: Observable, sampler: Observable) { + fileprivate let defaultValue: Element? + + init(source: Observable, sampler: Observable, defaultValue: Element? = nil) { self.source = source self.sampler = sampler + self.defaultValue = defaultValue } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { - let sink = SampleSequenceSink(parent: self, observer: observer, cancel: cancel) + let sink = SampleSequenceSink(parent: self, observer: observer, cancel: cancel, defaultValue: self.defaultValue) let subscription = sink.run() return (sink: sink, subscription: subscription) } diff --git a/Sources/AllTestz/main.swift b/Sources/AllTestz/main.swift index 30201e0b0..1eed490ec 100644 --- a/Sources/AllTestz/main.swift +++ b/Sources/AllTestz/main.swift @@ -1230,6 +1230,7 @@ final class ObservableSampleTest_ : ObservableSampleTest, RxTestCase { #endif static var allTests: [(String, (ObservableSampleTest_) -> () -> Void)] { return [ + ("testSample_Sampler_DefaultValue", ObservableSampleTest.testSample_Sampler_DefaultValue), ("testSample_Sampler_SamplerThrows", ObservableSampleTest.testSample_Sampler_SamplerThrows), ("testSample_Sampler_Simple1", ObservableSampleTest.testSample_Sampler_Simple1), ("testSample_Sampler_Simple2", ObservableSampleTest.testSample_Sampler_Simple2), diff --git a/Tests/RxSwiftTests/Observable+SampleTests.swift b/Tests/RxSwiftTests/Observable+SampleTests.swift index 1cebe86d5..ed1643f09 100644 --- a/Tests/RxSwiftTests/Observable+SampleTests.swift +++ b/Tests/RxSwiftTests/Observable+SampleTests.swift @@ -14,6 +14,52 @@ class ObservableSampleTest : RxTest { } extension ObservableSampleTest { + func testSample_Sampler_DefaultValue() { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createHotObservable([ + .next(150, 1), + .next(220, 2), + .next(240, 3), + .next(290, 4), + .next(300, 5), + .next(310, 6), + .completed(400) + ]) + + let ys = scheduler.createHotObservable([ + .next(150, ""), + .next(210, "bar"), + .next(250, "foo"), + .next(260, "qux"), + .next(320, "baz"), + .completed(500) + ]) + + let res = scheduler.start { + xs.sample(ys, defaultValue: 0) + } + + let correct = Recorded.events( + .next(210, 0), + .next(250, 3), + .next(260, 0), + .next(320, 6), + .next(500, 0), + .completed(500) + ) + + XCTAssertEqual(res.events, correct) + + XCTAssertEqual(xs.subscriptions, [ + Subscription(200, 400) + ]) + + XCTAssertEqual(ys.subscriptions, [ + Subscription(200, 500) + ]) + } + func testSample_Sampler_SamplerThrows() { let scheduler = TestScheduler(initialClock: 0)