From df14b40aecc55c370a2c45ea479e6e08fbfdd530 Mon Sep 17 00:00:00 2001 From: Anders Ha Date: Sun, 11 Jun 2017 17:52:39 +0800 Subject: [PATCH 1/2] Make the initial value of `combinePrevious` optional. --- CHANGELOG.md | 2 ++ Sources/Signal.swift | 33 +++++++++++++++++------ Sources/SignalProducer.swift | 20 +++++++------- Tests/ReactiveSwiftTests/SignalSpec.swift | 23 +++++++++++++--- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f370d8e12..73d7ea896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # master *Please add new entries at the top.* +1. `combinePrevious` for `Signal` and `SignalProducer` no longer requires an initial value. The first tuple would be emitted as soon as the second value is received by the operator if no initial value is given. (#445, kudos to @andersio) + 1. In Swift 3.2 or later, you may create `BindingTarget` for a key path of a specific object. (#440, kudos to @andersio) # 2.0.0-alpha.2 diff --git a/Sources/Signal.swift b/Sources/Signal.swift index 331a7ca42..399605190 100644 --- a/Sources/Signal.swift +++ b/Sources/Signal.swift @@ -1444,22 +1444,39 @@ extension Signal { /// Forward events from `self` with history: values of the returned signal /// are a tuples whose first member is the previous value and whose second member - /// is the current value. `initial` is supplied as the first member when `self` - /// sends its first value. + /// is the current value. + /// + /// If an initial value is given, the returned `Signal` would emit tuples as soon as + /// the first value is received. If `initial` is nil, the returned `Signal` would not + /// emit any tuple until it has received at least two values. /// /// - parameters: - /// - initial: A value that will be combined with the first value sent by - /// `self`. + /// - initial: An optional initial value. /// /// - returns: A signal that sends tuples that contain previous and current /// sent values of `self`. - public func combinePrevious(_ initial: Value) -> Signal<(Value, Value), Error> { - return scan((initial, initial)) { previousCombinedValues, newValue in - return (previousCombinedValues.1, newValue) + public func combinePrevious(_ initial: Value? = nil) -> Signal<(Value, Value), Error> { + return Signal<(Value, Value), Error> { observer in + var previous = initial + + return self.observe { event in + switch event { + case let .value(value): + if let previous = previous { + observer.send(value: (previous, value)) + } + previous = value + case .completed: + observer.sendCompleted() + case let .failed(error): + observer.send(error: error) + case .interrupted: + observer.sendInterrupted() + } + } } } - /// Combine all values from `self`, and forward only the final accumuated result. /// /// See `scan(_:_:)` if the resulting producer needs to forward also the partial diff --git a/Sources/SignalProducer.swift b/Sources/SignalProducer.swift index 6676f4905..3d66b3186 100644 --- a/Sources/SignalProducer.swift +++ b/Sources/SignalProducer.swift @@ -978,18 +978,20 @@ extension SignalProducer { return liftRight(Signal.skip(until:))(trigger.producer) } - /// Forward events from `self` with history: values of the returned producer - /// are a tuple whose first member is the previous value and whose second - /// member is the current value. `initial` is supplied as the first member - /// when `self` sends its first value. + /// Forward events from `self` with history: values of the produced signal + /// are a tuples whose first member is the previous value and whose second member + /// is the current value. + /// + /// If an initial value is given, the produced `Signal` would emit tuples as soon as + /// the first value is received. If `initial` is nil, the produced `Signal` would not + /// emit any tuple until it has received at least two values. /// /// - parameters: - /// - initial: A value that will be combined with the first value sent by - /// `self`. + /// - initial: An optional initial value. /// - /// - returns: A producer that sends tuples that contain previous and - /// current sent values of `self`. - public func combinePrevious(_ initial: Value) -> SignalProducer<(Value, Value), Error> { + /// - returns: A producer that sends tuples that contain previous and current + /// sent values of `self`. + public func combinePrevious(_ initial: Value? = nil) -> SignalProducer<(Value, Value), Error> { return lift { $0.combinePrevious(initial) } } diff --git a/Tests/ReactiveSwiftTests/SignalSpec.swift b/Tests/ReactiveSwiftTests/SignalSpec.swift index 4a68389ad..fb2f31a89 100755 --- a/Tests/ReactiveSwiftTests/SignalSpec.swift +++ b/Tests/ReactiveSwiftTests/SignalSpec.swift @@ -2756,6 +2756,7 @@ class SignalSpec: QuickSpec { } describe("combinePrevious") { + var signal: Signal! var observer: Signal.Observer! let initialValue: Int = 0 var latestValues: (Int, Int)? @@ -2763,12 +2764,13 @@ class SignalSpec: QuickSpec { beforeEach { latestValues = nil - let (signal, baseObserver) = Signal.pipe() - observer = baseObserver - signal.combinePrevious(initialValue).observeValues { latestValues = $0 } + let (baseSignal, baseObserver) = Signal.pipe() + (signal, observer) = (baseSignal, baseObserver) } - it("should forward the latest value with previous value") { + it("should forward the latest value with previous value with the given initial value") { + signal.combinePrevious(initialValue).observeValues { latestValues = $0 } + expect(latestValues).to(beNil()) observer.send(value: 1) @@ -2779,6 +2781,19 @@ class SignalSpec: QuickSpec { expect(latestValues?.0) == 1 expect(latestValues?.1) == 2 } + + it("should forward the latest value with previous value without any initial value") { + signal.combinePrevious().observeValues { latestValues = $0 } + + expect(latestValues).to(beNil()) + + observer.send(value: 1) + expect(latestValues).to(beNil()) + + observer.send(value: 2) + expect(latestValues?.0) == 1 + expect(latestValues?.1) == 2 + } } describe("combineLatest") { From 99cfe8919b85766b948d593347e9bddb7768dda8 Mon Sep 17 00:00:00 2001 From: Anders Ha Date: Mon, 12 Jun 2017 00:19:00 +0800 Subject: [PATCH 2/2] Resurrect the original `combinePrevious`. --- Sources/Signal.swift | 33 ++++++++++++++++++----- Sources/SignalProducer.swift | 27 +++++++++++++------ Tests/ReactiveSwiftTests/SignalSpec.swift | 2 +- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/Sources/Signal.swift b/Sources/Signal.swift index 399605190..ec1d30542 100644 --- a/Sources/Signal.swift +++ b/Sources/Signal.swift @@ -1444,18 +1444,37 @@ extension Signal { /// Forward events from `self` with history: values of the returned signal /// are a tuples whose first member is the previous value and whose second member - /// is the current value. - /// - /// If an initial value is given, the returned `Signal` would emit tuples as soon as - /// the first value is received. If `initial` is nil, the returned `Signal` would not - /// emit any tuple until it has received at least two values. + /// is the current value. `initial` is supplied as the first member when `self` + /// sends its first value. /// /// - parameters: - /// - initial: An optional initial value. + /// - initial: A value that will be combined with the first value sent by + /// `self`. /// /// - returns: A signal that sends tuples that contain previous and current /// sent values of `self`. - public func combinePrevious(_ initial: Value? = nil) -> Signal<(Value, Value), Error> { + public func combinePrevious(_ initial: Value) -> Signal<(Value, Value), Error> { + return combinePrevious(initial: initial) + } + + /// Forward events from `self` with history: values of the returned signal + /// are a tuples whose first member is the previous value and whose second member + /// is the current value. + /// + /// The returned `Signal` would not emit any tuple until it has received at least two + /// values. + /// + /// - returns: A signal that sends tuples that contain previous and current + /// sent values of `self`. + public func combinePrevious() -> Signal<(Value, Value), Error> { + return combinePrevious(initial: nil) + } + + /// Implementation detail of `combinePrevious`. A default argument of a `nil` initial + /// is deliberately avoided, since in the case of `Value` being an optional, the + /// `nil` literal would be materialized as `Optional.none` instead of `Value`, + /// thus changing the semantic. + private func combinePrevious(initial: Value?) -> Signal<(Value, Value), Error> { return Signal<(Value, Value), Error> { observer in var previous = initial diff --git a/Sources/SignalProducer.swift b/Sources/SignalProducer.swift index 3d66b3186..5707cdd16 100644 --- a/Sources/SignalProducer.swift +++ b/Sources/SignalProducer.swift @@ -978,21 +978,32 @@ extension SignalProducer { return liftRight(Signal.skip(until:))(trigger.producer) } + /// Forward events from `self` with history: values of the returned signal + /// are a tuples whose first member is the previous value and whose second member + /// is the current value. `initial` is supplied as the first member when `self` + /// sends its first value. + /// + /// - parameters: + /// - initial: A value that will be combined with the first value sent by + /// `self`. + /// + /// - returns: A signal that sends tuples that contain previous and current + /// sent values of `self`. + public func combinePrevious(_ initial: Value) -> SignalProducer<(Value, Value), Error> { + return lift { $0.combinePrevious(initial) } + } + /// Forward events from `self` with history: values of the produced signal /// are a tuples whose first member is the previous value and whose second member /// is the current value. /// - /// If an initial value is given, the produced `Signal` would emit tuples as soon as - /// the first value is received. If `initial` is nil, the produced `Signal` would not - /// emit any tuple until it has received at least two values. - /// - /// - parameters: - /// - initial: An optional initial value. + /// The produced `Signal` would not emit any tuple until it has received at least two + /// values. /// /// - returns: A producer that sends tuples that contain previous and current /// sent values of `self`. - public func combinePrevious(_ initial: Value? = nil) -> SignalProducer<(Value, Value), Error> { - return lift { $0.combinePrevious(initial) } + public func combinePrevious() -> SignalProducer<(Value, Value), Error> { + return lift { $0.combinePrevious() } } /// Combine all values from `self`, and forward the final result. diff --git a/Tests/ReactiveSwiftTests/SignalSpec.swift b/Tests/ReactiveSwiftTests/SignalSpec.swift index fb2f31a89..032f87f76 100755 --- a/Tests/ReactiveSwiftTests/SignalSpec.swift +++ b/Tests/ReactiveSwiftTests/SignalSpec.swift @@ -2768,7 +2768,7 @@ class SignalSpec: QuickSpec { (signal, observer) = (baseSignal, baseObserver) } - it("should forward the latest value with previous value with the given initial value") { + it("should forward the latest value with previous value with an initial value") { signal.combinePrevious(initialValue).observeValues { latestValues = $0 } expect(latestValues).to(beNil())