Skip to content

Latest commit

 

History

History
220 lines (168 loc) · 10.1 KB

File metadata and controls

220 lines (168 loc) · 10.1 KB

Framework Overview

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.

Events

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 Next event provides a new value from the source.
  • The Error event indicates that an error occurred before the signal could finish. The event may include an signal specific ErrorType object that indicates what went wrong. If no error can happen, the NoError type can be specified.
  • The Completed event indicates that the signal finished successfully, and that no more values will be sent by the source.
  • The Interrupted event indicates that the signal has terminated non-erroneous, yet unsuccessful e.g. when the corresponding request has been cancelled before the signal could finish.

Signals

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.

Pipes

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.

Signal Producers

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.

Buffers

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.

Observers

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.

Action

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.

Property

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 <~ Signal binds the signal to the property, updating the property's value to the latest value sent by the signal.
  • Property <~ SignalProducer creates 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 signal
  • Property <~ Property binds 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!

Disposables

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.

Schedulers

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.