Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A variant of combinePrevious that doesn't need an initial value. #445

Merged
merged 3 commits into from
Jun 13, 2017
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