Introduction to ReactiveCocoa
Overview of ReactiveCocoa ReactiveCocoa is inspired by functional reactive programming. Rather than using mutable variables which are replaced and modified in-place, RAC offers "event streams," represented by the Signal and SignalProducer types, that send values over time. ReactiveCocoa is inspired by Functional Reactive Programming (FRP). Instead of using mutable variables that change in place, RAC captures value changes through event streams, represented by and . Event streams unify all of Cocoa's common patterns for asynchrony and event handling, including: - Delegate methods - Callback blocks - NSNotifications - Control actions and responder chain events - Futures and promises - Key-value observing (KVO) In iOS development, we frequently respond to events to handle business logic — button taps, pull-to-refresh, network requests, property changes (via KVO), or user location updates (via CoreLocation). However, all these events are handled in different ways: actions, delegates, KVO, callbacks, etc. In fact, all of these events can be handled through RAC. ReactiveCocoa provides many methods for handling events, and using RAC makes event handling very convenient. You can place the handling code and the listening code in the same place, making management much easier without needing to jump to separate methods. This aligns well with the high cohesion, low coupling principle in software development. Common ReactiveCocoa Classes The signal class generally represents data that will be delivered in the future. Whenever data changes, the signal receives it internally and emits it immediately. Note: it's the data that is emitted, not the signal class itself. - A signal class () only indicates that data will be sent when it changes. The signal itself does not have the ability to send; it delegates that to an internal subscriber. - By default, a signal is a cold signal — it won't trigger even if the value changes. Only after a subscription is made does it become a hot signal and start triggering on value changes. - How to subscribe to a signal: call on the . Steps for using RACSignal: 1. Create a signal: 2. Send a signal: 3. Subscribe to the signal to activate it: RACSignal internals: 1. A subclass signal is created. First, the block is saved in , but it is not yet triggered (cold signal). 2. When the signal is subscribed — i.e., is called on the — internally creates a subscriber and saves the into it. At this point it becomes a hot signal. 3. internally calls the block of . Typically, is called inside . 4. under the hood executes the subscriber's . 5. Mark the signal as completed or unsubscribe. 6. Execute the code in 's . Code: Represents a subscriber. It is used to send signals. This is a protocol, not a class — any object that conforms to this protocol and implements its methods can act as a subscriber. Signals created via all have a subscriber that helps them send data. Used to cancel subscriptions or clean up resources. It is triggered automatically when a signal completes or sends an error. A signal provider that can both act as a signal and send signals. Use case: commonly used to replace delegates. With , you no longer need to define a delegate. Steps for using RACSubject 1. Create a signal: — unlike , there is no block when creating the signal. 2. Subscribe to the signal: . 3. Send a signal: . internals differ from : 1. Calling to subscribe only saves the subscriber. The subscriber's is already set. 2. Calling to send a signal iterates over all saved subscribers and calls each one's . Code: Using to replace a delegate: RACReplaySubject A replay signal provider; a subclass of . Use case 1: If a signal needs to re-send all previously emitted values each time it is subscribed to, use the replay signal class. Use case 2: You can set a to limit the number of cached values — only the most recent N values are cached. 1. Create a signal: — unlike , there is no block when creating the signal. 2. You can subscribe before or after sending. 1. Subscribe: 2. Send: internals differ from : 1. Calling saves the value and then iterates over all saved subscribers, calling each one's . 2. Calling iterates over all saved values and calls the subscriber's for each one. If you want a signal to replay all previous values when subscribed, you need to send before subscribing — in other words, save the values first, then subscribe. Code: From the output, you can see that regardless of whether comes before or after the subscription, the output is the same. With , the same code order would produce: That is, the subscriber added after receives nothing. RACTuple A tuple class, similar to , used to wrap values. The macro is specifically used to unpack tuples. The right side of the equals sign is the tuple to unpack. The macro's parameters specify the types to unpack into. The number of macro parameters must match the number of values in the tuple. RACSequence A collection class in RAC, used to replace and . It can be used to quickly iterate over arrays and dictionaries, and is useful for mapping dictionaries to model objects. Dictionary to model mapping: RACCommand A class in RAC used to handle events. It wraps event handling logic and data passing into a single object, and makes it easy to monitor the execution state of an event. Use cases: monitoring button taps, network requests. - RACCommand usage steps: 1. Create a command: 2. Inside the , create a and return it as the 's return value. 3. Execute the command: - RACCommand usage notes: 1. must return a signal; it cannot return nil. 2. If you don't want to pass any signal data, create an empty signal: . 3. The signal inside must call when data transmission is complete, otherwise the command will remain in the "executing" state indefinitely. 4. must be strongly referenced; otherwise, you won't receive signals from within it, because 's signals are sent lazily. - RACCommand design rationale: why does the internal return a signal, and what is that signal used for? 1. In RAC development, network requests are typically wrapped inside a . Executing a sends the request. 2. When receives data from the request, it needs to pass that data to the outside world — which is done via the signal returned by . - How to get data from the signal returned by RACCommand: 1. has an execution signal source. This is a signal of signals — the data emitted by this signal is itself a signal, not a plain value. 2. Subscribing to gives you the signal returned by . Then subscribing to the 's returned signal gives you the actual emitted values. - Monitor whether the current command is executing via . RACMulticastConnection Used when a signal is subscribed to multiple times. To avoid triggering the signal creation block multiple times and causing side effects, use this class. Usage note: is created via 's or method. - RACMulticastConnection usage steps: 1. Create a signal: 2. Create a connection: 3. Subscribe to the signal. Note: you subscribe to the connection's signal, not the original signal. 4. Connect: - RACMulticastConnection internals: 1. Creating gives: - (original signal), -. 2. Subscribing to calls 's , creating and saving a subscriber without executing the block. 3. internally subscribes to the original , with as the subscriber. 1. Subscribing to the original signal invokes its . 2. Inside , calling on the subscriber actually calls 's . 4. 's iterates over all of 's subscribers and sends the signal. 1. Since step 2 involved subscribing to , all subscribers from step 2 are retrieved and their is called. Problem: Suppose a signal wraps a network request. Every subscription triggers a new request, leading to multiple requests. Solution: Use to fix this. The output shows the request is sent twice — once per subscription. RACScheduler A queue in RAC, implemented using GCD. RACUnit Represents a stream that contains no meaningful value. When you see this, you can think of it as . RACEvent Wraps data into a signal event (). It is primarily used via 's method. References: Framework Overview Getting Started with ReactiveCocoa — The Basics Code: All code from this article can be found on my GitHub at .