This document contains a high-level description of the different components within the ReactiveCocoa framework, and an attempt to explain how they work together and divide responsibilities. This is meant to be a starting point for learning about new modules and finding more specific documentation.
For examples and help understanding how to use RAC, see the README or the Design Guidelines.
An event, represented by the Event class, is the formalized representation of the fact that something has happened. In Reactive Cocoa, events are the center piece of communication. An event might represent the press of a button, a piece of information received from an API, the occurrence of an error or the completion of a long running operation. A source generates events and sends them over a signal to any number of observers.
Event is an enumerated type representing either a value or one of three
terminal events.
- The
Nextevent provides a new value from the source. - The
Errorevent indicates that an error occurred before the signal could finish. The event may include an signal specificErrorTypeobject that indicates what went wrong. If no error can happen, theNoErrortype can be specified. - The
Completedevent indicates that the signal finished successfully, and that no more values will be sent by the source. - The
Interruptedevent indicates that the signal has terminated non-erroneous, yet unsuccessful e.g. when the corresponding request has been cancelled before the signal could finish.
A signal, represented by the Signal class, is any series of events over time that can be observed.
Signals are generally used to represent event streams that are already “in progress”, like notifications, user input, etc. As work is performed or data is received, events are sent on the signal, which pushes them out to any observers. All observers see the events at the same time.
Users must observe a signal in order to access its events. Observing a signal does not trigger any side effects. In other words, signals are entirely producer-driven and push-based, and consumers (observers) cannot have any effect on their lifetime. While observing a signal, the user can only evaluate the events in the same order as they are sent on the signal - there is no random access to values of the stream.
Signals can be manipulated by applying primitives to them.
Typical primitives to manipulate a single signal like filter, map and
reduce are available as well as primitives to manipulate multiple signals
at once (zip). Primitives operate only on the next events of a signal.
The |> operator is used to apply primitives to a signal. It can also be used
to compose basic primitives into more complex ones.
The lifetime of a signal consists of any number of next events, followed by
one terminating event: error, completed or interrupted
(but no combination of those).
Terminating events are not included in the streams values, they must be
handled specially.
A Pipe, created by Signal.pipe(), is a signal
that can be manually controlled.
The method returns a Signal and an Event Sink. This signal can be controlled by sending events to the sink. This can be extremely useful for bridging non-RAC code into the world of signals.
For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared observer instead. The signal can be returned, hiding the implementation detail of the callbacks.
A signal producer, represented by the SignalProducer class, creates signals and performs side effects.
They can be used to represent operations or tasks, like network
requests, where each invocation of start() will create a new underlying
operation. The produced signal is returned to the caller, who can observe
the result of the task by observing the signal.
Because of the behavior of start(), different signals created from the
producer may see a different version of events, the events may arrive in a
different order between signals, or the stream might be completely different!
However, this behavior ensures that consumers will receive the results,
in contrast to a plain signal that might send results before any observers
are attached.
Starting a signal returns a disposable which can be used to interrupt/cancel the work associated signal.
Just like signals, signal producers can also be manipulated via primitives
like map, filter, etc.
Every signal primitive can be lifted to operate upon signal producers instead.
In other words, lift creates a new signal producer which will apply the given
signal primitive to every signal created from the producer.
Furthermore, there are additional primitives that control when and how work
is started, for example times.
Again, the |> operator can be used to apply primitives to a signal
producer which implicitly lifts the primitive.
A Buffer, created by SignalProducer.buffer(), is a (optionally bounded)
queue for Events and replays those events when new
Signals are created from the producer.
Similar to a Pipe, the method returns an Observer. Events sent to this observer will be added to the queue. If the buffer is already at capacity, the earliest (oldest) event will be dropped to make room for the new event.
This can be useful to buffer events for future observers, like when a network request finishes before anything is ready to handle the result.
An Observer is anything that is waiting or capable of waiting for events from a signal. Within RAC, an observer is represented as an instance of the SinkOf struct with an input type of Event.
A signal can be observed by calling its observe method, providing either a
sink or callbacks for the different types of events as a parameter.
An Action, represented by the Action class, will do some work when executed with an Input. During or after execution, zero or more Output values are generated. Alternatively, an Error may be generated.
Actions are suited to perform side-effecting work as the user interacts with the app.
Usually the trigger for an Action is UI-driven, like when a button is clicked. Actions can also be automatically disabled based on a signal, and this disabled state can be represented in a UI by disabling any controls associated with the action.
For interaction with UIKit or AppKit GUI controls such as NSControl or
UIControl, RAC provides CocoaAction to wrap Action with KVO or
Cocoa Bindings.
A Property, represented by the PropertyType protocol, wraps a value, allowing observation of value changes.
The current value of a property can be obtained from the value getter. The
producer getter returns a SignalProducer that will send
the property's current value, followed by all changes over time.
The <~ operator can be used to bind properties in different ways. Note that in
all cases, the target has to be a MutablePropertyType.
Property <~ Signalbinds the signal to the property, updating the property's value to the latest value sent by the signal.Property <~ SignalProducercreates a signal from the given producer, which will immediately bind to the given property, updating the property's value to the latest value sent by the signalProperty <~ Propertybinds the destination property to the latest values the source property.
When bridging to Objective C code (like UIKit or AppKit), DynamicProperty can be used to
wrap a dynamic property using Key-Value-Coding and Key-Value-Observing. DynamicProperty
should only be used when KVO/KVC is required by the API used (e.g. NSOperation),
MutableProperty should be preferred whenever possible!
A Disposable, represented by the Disposable protocol, is a a mechanism for memory management and cancellation.
When starting a SignalProducer, a disposable will be returned.
This disposable can be used by the caller to cancel the work that has been started
(e.g. background processing, network requests, etc.). Additionally, all temporary
resources should be cleaned up and an Interrupted event is sent.
Observing a Signal may also return a disposable. Disposing it will prevent the observer from receiving any future events from that signal, it will however not have any effect on the signal itself.
For more information about cancellation, see the RAC Design Guidelines.
A scheduler, represented by the SchedulerType protocol, is a serial execution queue to perform work or deliver results upon.
Signals can be ordered to deliver events a a specific scheduler or SignalProducers can be ordered to start their work on a specific scheduler.
Schedulers are similar to Grand Central Dispatch queues, but schedulers support cancellation (via disposables), and always execute serially. With the exception of the ImmediateScheduler, schedulers do not offer synchronous execution. This helps avoid deadlocks, and encourages the use of signal operators instead of blocking work.
Schedulers are also somewhat similar to NSOperationQueue, but schedulers
do not allow tasks to be reordered or depend on one another.