Skip to content

Commit 98cf568

Browse files
authored
Merge pull request #445 from ReactiveCocoa/combine-previous
A variant of `combinePrevious` that doesn't need an initial value.
2 parents 104ec0b + d09ff7e commit 98cf568

File tree

4 files changed

+79
-13
lines changed

4 files changed

+79
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# master
22
*Please add new entries at the top.*
33

4+
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)
5+
46
1. Fixed an impedance mismatch in the `Signal` internals that caused heap corruptions. (#449, kudos to @gparker42)
57

68
1. In Swift 3.2 or later, you may create `BindingTarget` for a key path of a specific object. (#440, kudos to @andersio)

Sources/Signal.swift

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,11 +1457,47 @@ extension Signal {
14571457
/// - returns: A signal that sends tuples that contain previous and current
14581458
/// sent values of `self`.
14591459
public func combinePrevious(_ initial: Value) -> Signal<(Value, Value), Error> {
1460-
return scan((initial, initial)) { previousCombinedValues, newValue in
1461-
return (previousCombinedValues.1, newValue)
1462-
}
1460+
return combinePrevious(initial: initial)
1461+
}
1462+
1463+
/// Forward events from `self` with history: values of the returned signal
1464+
/// are a tuples whose first member is the previous value and whose second member
1465+
/// is the current value.
1466+
///
1467+
/// The returned `Signal` would not emit any tuple until it has received at least two
1468+
/// values.
1469+
///
1470+
/// - returns: A signal that sends tuples that contain previous and current
1471+
/// sent values of `self`.
1472+
public func combinePrevious() -> Signal<(Value, Value), Error> {
1473+
return combinePrevious(initial: nil)
14631474
}
14641475

1476+
/// Implementation detail of `combinePrevious`. A default argument of a `nil` initial
1477+
/// is deliberately avoided, since in the case of `Value` being an optional, the
1478+
/// `nil` literal would be materialized as `Optional<Value>.none` instead of `Value`,
1479+
/// thus changing the semantic.
1480+
private func combinePrevious(initial: Value?) -> Signal<(Value, Value), Error> {
1481+
return Signal<(Value, Value), Error> { observer in
1482+
var previous = initial
1483+
1484+
return self.observe { event in
1485+
switch event {
1486+
case let .value(value):
1487+
if let previous = previous {
1488+
observer.send(value: (previous, value))
1489+
}
1490+
previous = value
1491+
case .completed:
1492+
observer.sendCompleted()
1493+
case let .failed(error):
1494+
observer.send(error: error)
1495+
case .interrupted:
1496+
observer.sendInterrupted()
1497+
}
1498+
}
1499+
}
1500+
}
14651501

14661502
/// Combine all values from `self`, and forward only the final accumuated result.
14671503
///

Sources/SignalProducer.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -978,21 +978,34 @@ extension SignalProducer {
978978
return liftRight(Signal.skip(until:))(trigger.producer)
979979
}
980980

981-
/// Forward events from `self` with history: values of the returned producer
982-
/// are a tuple whose first member is the previous value and whose second
983-
/// member is the current value. `initial` is supplied as the first member
984-
/// when `self` sends its first value.
981+
/// Forward events from `self` with history: values of the returned signal
982+
/// are a tuples whose first member is the previous value and whose second member
983+
/// is the current value. `initial` is supplied as the first member when `self`
984+
/// sends its first value.
985985
///
986986
/// - parameters:
987987
/// - initial: A value that will be combined with the first value sent by
988988
/// `self`.
989989
///
990-
/// - returns: A producer that sends tuples that contain previous and
991-
/// current sent values of `self`.
990+
/// - returns: A signal that sends tuples that contain previous and current
991+
/// sent values of `self`.
992992
public func combinePrevious(_ initial: Value) -> SignalProducer<(Value, Value), Error> {
993993
return lift { $0.combinePrevious(initial) }
994994
}
995995

996+
/// Forward events from `self` with history: values of the produced signal
997+
/// are a tuples whose first member is the previous value and whose second member
998+
/// is the current value.
999+
///
1000+
/// The produced `Signal` would not emit any tuple until it has received at least two
1001+
/// values.
1002+
///
1003+
/// - returns: A producer that sends tuples that contain previous and current
1004+
/// sent values of `self`.
1005+
public func combinePrevious() -> SignalProducer<(Value, Value), Error> {
1006+
return lift { $0.combinePrevious() }
1007+
}
1008+
9961009
/// Combine all values from `self`, and forward the final result.
9971010
///
9981011
/// See `scan(_:_:)` if the resulting producer needs to forward also the partial

Tests/ReactiveSwiftTests/SignalSpec.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,19 +2756,21 @@ class SignalSpec: QuickSpec {
27562756
}
27572757

27582758
describe("combinePrevious") {
2759+
var signal: Signal<Int, NoError>!
27592760
var observer: Signal<Int, NoError>.Observer!
27602761
let initialValue: Int = 0
27612762
var latestValues: (Int, Int)?
27622763

27632764
beforeEach {
27642765
latestValues = nil
27652766

2766-
let (signal, baseObserver) = Signal<Int, NoError>.pipe()
2767-
observer = baseObserver
2768-
signal.combinePrevious(initialValue).observeValues { latestValues = $0 }
2767+
let (baseSignal, baseObserver) = Signal<Int, NoError>.pipe()
2768+
(signal, observer) = (baseSignal, baseObserver)
27692769
}
27702770

2771-
it("should forward the latest value with previous value") {
2771+
it("should forward the latest value with previous value with an initial value") {
2772+
signal.combinePrevious(initialValue).observeValues { latestValues = $0 }
2773+
27722774
expect(latestValues).to(beNil())
27732775

27742776
observer.send(value: 1)
@@ -2779,6 +2781,19 @@ class SignalSpec: QuickSpec {
27792781
expect(latestValues?.0) == 1
27802782
expect(latestValues?.1) == 2
27812783
}
2784+
2785+
it("should forward the latest value with previous value without any initial value") {
2786+
signal.combinePrevious().observeValues { latestValues = $0 }
2787+
2788+
expect(latestValues).to(beNil())
2789+
2790+
observer.send(value: 1)
2791+
expect(latestValues).to(beNil())
2792+
2793+
observer.send(value: 2)
2794+
expect(latestValues?.0) == 1
2795+
expect(latestValues?.1) == 2
2796+
}
27822797
}
27832798

27842799
describe("combineLatest") {

0 commit comments

Comments
 (0)