Exploring Signal and SignalProducer in ReactiveSwift

Mar 18, 2017: Updated for ReactiveSwift 1.1 (ReactiveCocoa 5)

I’m picking up reactive programming with ReactiveSwift and one of the most confusing parts is learning the difference between a Signal and a SignalProducer.

We can create a Swift playground to experiment with the two types. We’ll use MutableProperty to manipulate the streams and produce the Signal and SignalProducer objects.

let a = MutableProperty<String>("")
let b = MutableProperty<String>("")
let c = MutableProperty<String>("")

We start by creating three MutableProperty objects initialized with empty strings. We can manipulate each stream by changing the value of the MutableProperty.

let signalDisposable = Signal.combineLatest(a.signal, b.signal, c.signal)
.observeValues { aVal, bVal, cVal in
print("combined signal = \(aVal + bVal + cVal)")
a.value = "πŸ–"
b.value = "🌏"
// No output, still awaiting value from C to combine
c.value = "πŸ”"
// Output: combined signal = πŸ–πŸŒπŸ”
// Values from all 3 signals have been sent and combined
a.value = "πŸ•"
// Output: combined signal = πŸ•πŸŒπŸ”
b.value = "🌞"
// No output because the combined signal has been disposed

For the first experiment, we combine the three signals into one and output the combined value whenever it changes. Notice how the Signal method observeValues executes its block only after all three signals have sent new values.

var disposable = SignalProducer.combineLatest(a.producer, b.producer, c.producer)
.startWithValues { aVal, bVal, cVal in
print("combined producer = \(aVal + bVal + cVal)")
// Output: combined producer = πŸ•πŸŒžπŸ”
// SignalProducers create side-effects. The initial values are sent when the SignalProducer starts.
c.value = "😸"
// Output: combined producer = πŸ•πŸŒžπŸ˜Έ

Next, let’s combine the streams with SignalProducer. The SignalProducer method startWithValues combines the current values from the three streams and sends a value immediately.

disposable = a.producer
.lift { aSignal in
return Signal.combineLatest(aSignal, b.signal, c.signal)
.startWithValues { aVal, bVal, cVal in
print("mixed producer = \(aVal + bVal + cVal)")
// Nothing is printed because signals B and C have not sent any values
b.value = "πŸ‡"
c.value = "πŸ‡"
// Output: mixed producer = πŸ•πŸ‡πŸ‡
// All values have been sent (A has already sent an initial value because it's a SignalProducer)
a.value = "πŸ‡"
// Output: mixed producer = πŸ‡πŸ‡πŸ‡

Let’s mix things up a bit. For the last experiment, we combine a stream as a SignalProducer with the other two as Signals. The combined stream only produces an output after the two signal streams have sent their values. There’s no need to send another value from stream A because the producer sends the stored value the MutableProperty that created it.

Here’s the full gist if you’d like to try it out for yourself.