Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
42 changes: 39 additions & 3 deletions Sources/Signal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1454,11 +1454,47 @@ extension Signal {
/// - 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)
}
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<Value>.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

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.
///
Expand Down
25 changes: 19 additions & 6 deletions Sources/SignalProducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -978,21 +978,34 @@ 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 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 producer that sends tuples that contain previous and
/// current sent values of `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.
///
/// 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() -> SignalProducer<(Value, Value), Error> {
return lift { $0.combinePrevious() }
}

/// Combine all values from `self`, and forward the final result.
///
/// See `scan(_:_:)` if the resulting producer needs to forward also the partial
Expand Down
23 changes: 19 additions & 4 deletions Tests/ReactiveSwiftTests/SignalSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2756,19 +2756,21 @@ class SignalSpec: QuickSpec {
}

describe("combinePrevious") {
var signal: Signal<Int, NoError>!
var observer: Signal<Int, NoError>.Observer!
let initialValue: Int = 0
var latestValues: (Int, Int)?

beforeEach {
latestValues = nil

let (signal, baseObserver) = Signal<Int, NoError>.pipe()
observer = baseObserver
signal.combinePrevious(initialValue).observeValues { latestValues = $0 }
let (baseSignal, baseObserver) = Signal<Int, NoError>.pipe()
(signal, observer) = (baseSignal, baseObserver)
}

it("should forward the latest value with previous value") {
it("should forward the latest value with previous value with an initial value") {
signal.combinePrevious(initialValue).observeValues { latestValues = $0 }

expect(latestValues).to(beNil())

observer.send(value: 1)
Expand All @@ -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") {
Expand Down