Core Reactive Primitives

  1. Signal
  2. Event
  3. SignalProducer
  4. Property
  5. Action
  6. Lifetime

Signal: a unidirectional stream of events.

The owner of a Signal has unilateral control of the event stream. Observers may register their interests in the future events at any time, but the observation would have no side effect on the stream or its owner.

It is like a live TV feed — you can observe and react to the content, but you cannot have a side effect on the live feed or the TV station.

let channel: Signal<Program, NoError> = tvStation.channelOne
channel.observeValues { program in ... }

See also: The Signal overview, The Signal contract, The Signal API reference

Event: the basic transfer unit of an event stream.

A Signal may have any arbitrary number of events carrying a value, following by an eventual terminal event of a specific reason.

It is like a frame in a one-time live feed — seas of data frames carry the visual and audio data, but the feed would eventually be terminated with a special frame to indicate end of stream.

See also: The Event overview, The Event contract, The Event API reference

SignalProducer: deferred work that creates a stream of values.

SignalProducer defers work — of which the output is represented as a stream of values — until it is started. For every invocation to start the SignalProducer, a new Signal is created and the deferred work is subsequently invoked.

It is like a on-demand streaming service — even though the episode is streamed like a live TV feed, you can choose what you watch, when to start watching and when to interrupt it.

let frames: SignalProducer<VideoFrame, ConnectionError> = vidStreamer.streamAsset(id: tvShowId)
let interrupter = frames.start { frame in ... }
interrupter.dispose()

See also: The SignalProducer overview, The SignalProducer contract, The SignalProducer API reference

Property: an observable box that always holds a value.

Property is a variable that can be observed for its changes. In other words, it is a stream of values with a stronger guarantee than Signal — the latest value is always available, and the stream would never fail.

It is like the continuously updated current time offset of a video playback — the playback is always at a certain time offset at any time, and it would be updated by the playback logic as the playback continues.

let currentTime: Property<TimeInterval> = video.currentTime
print("Current time offset: \(currentTime.value)")
currentTime.signal.observeValues { timeBar.timeLabel.text = "\($0)" }

See also: The Property overview, The Property contract, The property API reference

Action: a serialized worker with a preset action.

When being invoked with an input, Action apply the input and the latest state to the preset action, and pushes the output to any interested parties.

It is like an automatic vending machine — after choosing an option with coins inserted, the machine would process the order and eventually output your wanted snack. Notice that the entire process is mutually exclusive — you cannot have the machine to serve two customers concurrently.

// Purchase from the vending machine with a specific option.
vendingMachine.purchase
    .apply(snackId)
    .startWithResult { result
        switch result {
        case let .success(snack):
            print("Snack: \(snack)")

        case let .failure(error):
            // Out of stock? Insufficient fund?
            print("Transaction aborted: \(error)")
        }
    }

// The vending machine.
class VendingMachine {
    let purchase: Action<Int, Snack, VendingMachineError>
    let coins: MutableProperty<Int>

    // The vending machine is connected with a sales recorder.
    init(_ salesRecorder: SalesRecorder) {
        coins = MutableProperty(0)
        purchase = Action(state: coins, enabledIf: { $0 > 0 }) { coins, snackId in
            return SignalProducer { observer, _ in
                // The sales magic happens here.
                // Fetch a snack based on its id
            }
        }

        // The sales recorders are notified for any successful sales.
        purchase.values.observeValues(salesRecorder.record)
    }
}

See also: The Action overview, The Action API reference

Lifetime: limits the scope of an observation

When observing a Signal or SignalProducer, it doesn’t make sense to continue emitting values if there’s no longer anyone observing them. Consider the video stream: once you stop watching the video, the stream can be automatically closed by providing a Lifetime:

class VideoPlayer {
  private let (lifetime, token) = Lifetime.make()

  func play() {
    let frames: SignalProducer<VideoFrame, ConnectionError> = ...
    frames.take(during: lifetime).start { frame in ... }
  }
}

See also: The Lifetime overview, The Lifetime API reference