Understanding ReactiveCocoa: Core Elements, Signal Flow, Commands, and Channels
The article explains ReactiveCocoa’s core concepts—RACSignal, subscribers, cold versus hot signals, and key transformations such as bind, map, flatten and throttle—while showing how RACCommand encapsulates actions and RACChannel enables two‑way binding, together illustrating how to build expressive, maintainable data pipelines in iOS apps.
ReactiveCocoa (RAC) is a functional reactive programming framework for Cocoa that provides an alternative way of writing code, improving readability and stability. This article focuses on the data‑flow aspects of RAC, analyzing its core elements and how RAC Operations, RACCommand, and RACChannel work in a signal pipeline.
RAC Core Elements and Pipeline
The central concept of RAC is the RACSignal, which acts as a signal source. A RACSubscriber (the subscriber) receives events from the signal and processes them. The typical subscription process consists of three steps:
Creating the signal with createSignal: Creating the subscriber (worker)
Subscribing the subscriber to the signal
Example of creating a signal:
RACSignal *pipeline = [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
[subscriber sendNext:@(1)];
[subscriber sendNext:@(2)];
[subscriber sendNext:@(3)];
[subscriber sendCompleted]; // (1)
return [RACDisposable disposableWithBlock:^{ // (2)
NSLog(@"the pipeline has sent 3 values, and has been stopped");
}];
}];Creating a subscriber:
RACSubscriber *worker = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];Subscribing: RACDisposable *disposable = [pipeline subscribe:worker]; RAC also provides a convenience method subscribeNext:error:completed: that hides the subscriber creation.
Cold vs. Hot Signals
A cold signal (e.g., RACDynamicSignal) starts emitting values only when it is subscribed to. A hot signal (e.g., RACSubject) begins emitting as soon as it is created, regardless of subscribers. The article shows how RACSubject maintains an array of subscribers and uses methods such as sendNext:, sendError:, and sendComplete: to push values.
Example of sending a value from a hot signal:
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value]; // (1)
}];
}Cold‑to‑hot conversion can be done with [RACDynamicSignal multicast:RACSubject].
RAC Operations (Signal Transformations)
The article explains several key operations that transform signals: bind: – creates a new signal by subscribing to the original signal, converting each value into an intermediate signal, and forwarding that intermediate signal’s events. flattenMap: – a wrapper around bind that adds safety checks. map: – converts each value directly (implemented via flattenMap). flatten: – flattens a signal‑of‑signals into a single signal. switchToLatest: – follows only the most recent inner signal. scanWithStart:reduceWithIndex: – aggregates values by feeding the previous result into the next reduction. throttle: – emits a value only if no new value arrives within a specified time interval.
Example of bind: implementation:
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { // (1)
RACStreamBindBlock bindingBlock = block();
[self subscribeNext:^(id x) { // (2)
BOOL stop = NO;
id middleSignal = bindingBlock(x, &stop); // (3)
if (middleSignal != nil) {
RACDisposable *disposable = [middleSignal subscribeNext:^(id x) { // (4)
[subscriber sendNext:x]; // (5)
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
return nil;
}];
}These operations are combined to build a complete data‑flow for a sample search feature: throttling user input, creating a search signal, aggregating results with scanWithStart, and delivering updates on the main thread.
Example of the search‑flow code:
[[[self.searchBar rac_textSignal]
throttle:0.3]
subscribeNext:^(NSString*keyString) {
RACSignal *searchSignal = [self.viewModel createSearchSignalWithString:keyString];
[[[searchSignal
scanWithStart:[NSMutableArray array] reduce:^NSMutableArray *(NSMutableArray *running, NSArray *next) {
[running addObjectsFromArray:next];
return running;
}]
deliverOnMainThread]
subscribeNext:^(id x) {
// UI Processing
}];
}];RACCommand
RACCommand encapsulates an action, exposing executionSignals, executing, and errors. When execute: is called, a new signal is created, turned into a hot signal via a multicast connection, added to an internal array, and subscribed to. The command’s state can be observed to show loading indicators or handle errors.
Implementation excerpt:
- (RACSignal *)execute:(id)input {
RACSignal *signal = self.signalBlock(input); // (1)
RACMulticastConnection *connection = [[signal subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]]; // (3)
@weakify(self);
[self addActiveExecutionSignal:connection.signal]; // (2)
[connection.signal subscribeError:^(NSError *error) {
@strongify(self);
[self removeActiveExecutionSignal:connection.signal];
} completed:^{
@strongify(self);
[self removeActiveExecutionSignal:connection.signal];
}];
[connection connect]; // (4)
return [connection.signal]; // (5)
}Observing executing and errors is done via KVO on the internal activeExecutionSignals array and mapping the results.
RACChannel
RACChannel provides two‑way binding via its terminals ( leadingT and followingT). A terminal is both a signal and a subscriber, allowing values to flow in either direction. The most common usage is the RACChannelTo macro, which creates a channel bound to a property.
Example:
RACChannelTerminal *integerChannelT = RACChannelTo(self, integerProperty, @42);
[integerChannelT sendNext:@5]; // (1)
[integerChannelT subscribeNext:^(id value) { // (2)
NSLog(@"value: %@", value);
}];Various UIKit components expose RACChannel terminals (e.g., UITextField rac_newTextChannel), enabling seamless two‑way data binding between UI and view‑model.
Conclusion
The article dissects RAC’s subscription mechanism, core elements, signal flow, and key operations, demonstrating how to construct expressive, maintainable data pipelines in iOS applications. Understanding these low‑level details helps developers use ReactiveCocoa more effectively.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Meituan Technology Team
Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
