blob: 03a4ca0f35e05af750a07120143fc09701557165 [file] [log] [blame] [view] [edit]
# Basic Operators
This document explains some of the most common operators used in ReactiveCocoa,
and includes examples demonstrating their use.
Operators that apply to [sequences][Sequences] _and_ [signals][Signals] are
known as [stream][Streams] operators.
**[Performing side effects with signals](#performing-side-effects-with-signals)**
1. [Subscription](#subscription)
1. [Injecting effects](#injecting-effects)
**[Transforming streams](#transforming-streams)**
1. [Mapping](#mapping)
1. [Filtering](#filtering)
**[Combining streams](#combining-streams)**
1. [Concatenating](#concatenating)
1. [Flattening](#flattening)
1. [Mapping and flattening](#mapping-and-flattening)
**[Combining signals](#combining-signals)**
1. [Sequencing](#sequencing)
1. [Merging](#merging)
1. [Combining latest values](#combining-latest-values)
1. [Switching](#switching)
## Performing side effects with signals
Most signals start out "cold," which means that they will not do any work until
[subscription](#subscription).
Upon subscription, a signal or its [subscribers][Subscription] can perform _side
effects_, like logging to the console, making a network request, updating the
user interface, etc.
Side effects can also be [injected](#injecting-effects) into a signal, where
they won't be performed immediately, but will instead take effect with each
subscription later.
### Subscription
The [-subscribe…][RACSignal] methods give you access to the current and future values in a signal:
```objc
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// Outputs: A B C D E F G H I
[letters subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
```
For a cold signal, side effects will be performed once _per subscription_:
```objc
__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
subscriptions++;
[subscriber sendCompleted];
return nil;
}];
// Outputs:
// subscription 1
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u", subscriptions);
}];
// Outputs:
// subscription 2
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u", subscriptions);
}];
```
This behavior can be changed using a [connection][Connections].
### Injecting effects
The [-do…][RACSignal+Operations] methods add side effects to a signal without actually
subscribing to it:
```objc
__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
subscriptions++;
[subscriber sendCompleted];
return nil;
}];
// Does not output anything yet
loggingSignal = [loggingSignal doCompleted:^{
NSLog(@"about to complete subscription %u", subscriptions);
}];
// Outputs:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u", subscriptions);
}];
```
## Transforming streams
These operators transform a single stream into a new stream.
### Mapping
The [-map:][RACStream] method is used to transform the values in a stream, and
create a new stream with the results:
```objc
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
// Contains: AA BB CC DD EE FF GG HH II
RACSequence *mapped = [letters map:^(NSString *value) {
return [value stringByAppendingString:value];
}];
```
### Filtering
The [-filter:][RACStream] method uses a block to test each value, including it
into the resulting stream only if the test passes:
```objc
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 2 4 6 8
RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
return (value.intValue % 2) == 0;
}];
```
## Combining streams
These operators combine multiple streams into a single new stream.
### Concatenating
The [-concat:][RACStream] method appends one stream's values to another:
```objc
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];
```
### Flattening
The [-flatten][RACStream] operator is applied to a stream-of-streams, and
combines their values into a single new stream.
Sequences are [concatenated](#concatenating):
```objc
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *flattened = [sequenceOfSequences flatten];
```
Signals are [merged](#merging):
```objc
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
[subscriber sendNext:letters];
[subscriber sendNext:numbers];
[subscriber sendCompleted];
return nil;
}];
RACSignal *flattened = [signalOfSignals flatten];
// Outputs: A 1 B C 2
[flattened subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];
```
### Mapping and flattening
[Flattening](#flattening) isn't that interesting on its own, but understanding
how it works is important for [-flattenMap:][RACStream].
`-flattenMap:` is used to transform each of a stream's values into _a new
stream_. Then, all of the streams returned will be flattened down into a single
stream. In other words, it's [-map:](#mapping) followed by [-flatten](#flattening).
This can be used to extend or edit sequences:
```objc
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
RACSequence *extended = [numbers flattenMap:^(NSString *num) {
return @[ num, num ].rac_sequence;
}];
// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence *edited = [numbers flattenMap:^(NSString *num) {
if (num.intValue % 2 == 0) {
return [RACSequence empty];
} else {
NSString *newNum = [num stringByAppendingString:@"_"];
return [RACSequence return:newNum];
}
}];
```
Or create multiple signals of work which are automatically recombined:
```objc
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
[[letters
flattenMap:^(NSString *letter) {
return [database saveEntriesForLetter:letter];
}]
subscribeCompleted:^{
NSLog(@"All database entries saved successfully.");
}];
```
## Combining signals
These operators combine multiple signals into a single new [RACSignal][].
### Sequencing
[-then:][RACSignal+Operations] starts the original signal,
waits for it to complete, and then only forwards the values from a new signal:
```objc
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// The new signal only contains: 1 2 3 4 5 6 7 8 9
//
// But when subscribed to, it also outputs: A B C D E F G H I
RACSignal *sequenced = [[letters
doNext:^(NSString *letter) {
NSLog(@"%@", letter);
}]
then:^{
return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;
}];
```
This is most useful for executing all the side effects of one signal, then
starting another, and only returning the second signal's values.
### Merging
The [+merge:][RACSignal+Operations] method will forward the values from many
signals into a single stream, as soon as those values arrive:
```objc
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
// Outputs: A 1 B C 2
[merged subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];
```
### Combining latest values
The [+combineLatest:][RACSignal+Operations] and `+combineLatest:reduce:` methods
will watch multiple signals for changes, and then send the latest values from
_all_ of them when a change occurs:
```objc
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
combineLatest:@[ letters, numbers ]
reduce:^(NSString *letter, NSString *number) {
return [letter stringByAppendingString:number];
}];
// Outputs: B1 B2 C2 C3
[combined subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];
```
Note that the combined signal will only send its first value when all of the
inputs have sent at least one. In the example above, `@"A"` was never
forwarded because `numbers` had not sent a value yet.
### Switching
The [-switchToLatest][RACSignal+Operations] operator is applied to
a signal-of-signals, and always forwards the values from the latest signal:
```objc
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];
RACSignal *switched = [signalOfSignals switchToLatest];
// Outputs: A B 1 D
[switched subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[signalOfSignals sendNext:numbers];
[letters sendNext:@"C"];
[numbers sendNext:@"1"];
[signalOfSignals sendNext:letters];
[numbers sendNext:@"2"];
[letters sendNext:@"D"];
```
[Connections]: FrameworkOverview.md#connections
[RACSequence]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h
[RACSignal]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h
[RACSignal+Operations]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h
[RACStream]: ../ReactiveCocoaFramework/ReactiveCocoa/RACStream.h
[Sequences]: FrameworkOverview.md#sequences
[Signals]: FrameworkOverview.md#signals
[Streams]: FrameworkOverview.md#streams
[Subscription]: FrameworkOverview.md#subscription