Deep understanding replaces simple memory
ReactiveCocoa is a functional responsive programming framework
Responsive programming
- The opposite of reactive is active programming
- Take switch control light bulb as an example, active programming is to hold a light bulb in the switch class, when the switch changes, call the bulb method to control the light bulb on and off
- The response formula is different
- The switch, as the observed, only needs to provide an interface to represent the listener
- When the switch changes, you just need to tell the listener
- The light bulb, of course, is the listener, modifying its light on and off when it receives a message that the switch status changes
- Obviously, responsiveness reduces the coupling between the bulb and the switch
Functional programming
ReactiveCocoa
- ReactiveCocoa provides features to encode event-driven logic in a functional responsive form
- Event-driven logic, in iOS, can be delegate methods, target-action, kVO, notifications, synchronous asynchronous events, or custom signals. For example, an asynchronous network request logic might need to be handled via a delegate to retrieve the result of the request callback. We can encapsulate it as a RACsignal
RACSignal
- The basis for being able to do this is RACSignal
- RACSignal can literally mean a signal
- Signals can send many events
- There are three types of events: Next, Error, and complete
- The next event is sent multiple times, and Error and complete are mutually exclusive and sent only once
- Next is multiple because an event can happen multiple times, such as a BTN click event
- Responses to these events passTo subscribe toThese events —
subscribe
Series method - Multiple subscriptions can be made to a signal
- RAC adds signal events to many UI components by way of classification, which can be easily passed
rac_textSignal
Similar methods are used to get these signals
Flow of events
- At its core, reactive programming treats the logic of an event as a flow of data
- After an event occurs, data flows through multiple nodes to be processed. This is called event flow
- In the whole event flow, there may be multiple steps -map, filter, etc., each node can transform the data
- Each time an event stream passes through a node, it still returns a signal, so you can make a chain-like call
For example 1
Here is the story of two TextFields and a login button:
- The two textfields are username and password
- The number of characters entered by the two Textfields must be greater than 3 to be meaningful. Otherwise, the textfields will display a background color prompt and the login button cannot be clicked
The figure above is a complete representation of the event flow (or data flow)
- Two Textfields pass
rac_textSignal
Get the signal - The signal sends the next event, passing first
map
, verifies the text data and converts it to BOOL, which is further converted to UIColor to determine the background color of the TextField - At the same time, another branch of parallelism is done by applying two signals
combine
The combined operation results determine the status of the login button
This corresponds to the following code
/ / two textfield background color control RACSignal * validUsernameSignal = [self. UsernameTextField. Rac_textSignal map: ^ id (nsstrings * value) { return @([self isValidUsername:value]); }]; RAC(self.usernameTextField, backgroundColor) = [validUsernameSignal map:^id(NSNumber *isValidNumber) { return [isValidNumber boolValue] ? UIColor.clearColor : UIColor.yellowColor; }]; RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal map:^id(NSString *value) { return @([self isValidPassword:value]); }]; RAC(self.passwordTextField, backgroundColor) = [validPasswordSignal map:^id(NSNumber *isValidNumber) { return [isValidNumber boolValue] ? UIColor.clearColor : UIColor.yellowColor; }]; // login BTN enable Control // Combine two signals into one signal RACSignal *signUpActiveSignal = [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal] reduce:^id(NSNumber *isUsernameValidNumber, NSNumber *isPasswordValidNumber) { return @([isUsernameValidNumber boolValue] && [isPasswordValidNumber boolValue]); }]; [signUpActiveSignal subscribeNext:^(NSNumber *canSignUpNumber) { self.signInButton.enabled = [canSignUpNumber boolValue]; }]; // btn touchupinside [[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) { self.signInButton.enabled = NO; self.signInFailureText.hidden = YES; }] flattenMap:^id(id value) { return [self signInSignal]; }] subscribeNext:^(NSNumber *successNumber) { self.signInButton.enabled = YES; BOOL isSuccess = [successNumber boolValue]; self.signInFailureText.hidden = isSuccess; if (isSuccess) { [self performSegueWithIdentifier:@"signInSuccess" sender:self]; } }];Copy the code
For example 2
This example is the process of entering content in searchTextField, searching for Twitter data, and showing it
requestxxx
Get a custom request twitter data signal- through
then
Operation, waitcomplete
Event, otherwise the error message is sent directly to the last executor - After the Twitter data request is successful, the signal in the event stream is converted to the searchTextField
rac_textSignal
signal - for
filter
,throttle
And so on a series of operations throughflattenMap
In thesignalForSearchText
Create the signal that sends the request, and execute the request
This corresponds to the following code
[[[[[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] filter:^BOOL(NSString *text) { @strongify(self) return [self isValidSearchText:text]; }] throttle: 0.5] flattenMap: ^ RACStream * (nsstrings * text) {@ strongify (self) return [self signalForSearchWithText: text]; }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDictionary *jsonSearchResult) { NSArray *statuses = jsonSearchResult[@"statuses"]; NSArray *tweets = [statuses linq_select:^id(id tweet) { return [RWTweet tweetWithStatus:tweet]; }]; [self.resultsViewController displayTweets:tweets]; } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];Copy the code
Understand the flow of events in depth
- There are global variables inside ReactiveCocoa that store information such as signal
- If a signal is created but there are no subscribers, ReactiveCocoa does not store objects such as signals in the Pipline
- The SUBSCRIBE method returns one
RACDisposable
object - Normally, the subscription status is automatically removed after signal sends complete or error
RACDisposable
It can also be removed manually - When a signal in a pipline sends an error, the error is passed directly to the final error handling block
RACSignal common methods
methods | describe | note |
---|---|---|
map | – (instancetype)map:(id (^__strong)(__strong id))block; | Converts the data of the event sent by the signal |
flattenMap | – (instancetype)flattenMap:(RACStream * (^__strong)(__strong id))block; | When a block also returns a RACsigna, using a flattenMap can avoid getting racSignals-type data later on. The flattenMap unpacks the signal data |
combine | + (RACSignal *)combineLatest:(id)signals reduce:(id (^__strong)())reduceBlock; | Multiple signals are merged so that when any signal is sent to Next, the block executes |
then | – (RACSignal *)then:(RACSignal * (^__strong)(void))block; | The process in the pipline will not continue until the signal represented by recevier sends a complete or error event. If complete, the methods in the block will be executed and the signal will be converted to the signal generated in the block. If it is error, it is handed directly to the SubscribeError processing |
deliverOn | – (RACSignal *)deliverOn:(RACScheduler *)scheduler; | This method gets a signal, and subsequent events will be in the specified thread |
throttle | – (RACSignal *)throttle:(NSTimeInterval)interval; | Throttling controls, such as when entering a content search in a TextField, want to wait 500ms after entering a character before performing the search. This method is the next time after a specified period of time (seconds)next The event will be sent before it arrivesnext The event |
doNext | – (RACSignal *)doNext:(void (^__strong)(__strong id))block; | After signal sends the next event, the logic in the method block is executed. This method doesn’t need to return any data type, just gives the developer a chance to do somethingside-effect The things |
Use skills
RAC(self.passwordTextField, backgroundColor) = xxxsignal
The result of the xxxSignal event is assigned to the backgroundColor of the textField
doubt
RACSignal
thecreateSignal
The method declaration says that after the signal is created, once there are subscribers, thencreateSignal
For example, if you create a Twitter request for signal in this article, it will send a Twitter request immediately if it has a subscriber. Is that reasonable?
reference
- ReactiveCocoa Tutorial — The Definitive Introduction: Part 1/2