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 —subscribeSeries 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 passedrac_textSignalSimilar 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:

  1. The two textfields are username and password
  2. 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)

  1. Two Textfields passrac_textSignalGet the signal
  2. The signal sends the next event, passing firstmap, verifies the text data and converts it to BOOL, which is further converted to UIColor to determine the background color of the TextField
  3. At the same time, another branch of parallelism is done by applying two signalscombineThe 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

  1. requestxxxGet a custom request twitter data signal
  2. throughthenOperation, waitcompleteEvent, otherwise the error message is sent directly to the last executor
  3. After the Twitter data request is successful, the signal in the event stream is converted to the searchTextFieldrac_textSignalsignal
  4. forfilter,throttleAnd so on a series of operations throughflattenMapIn thesignalForSearchTextCreate 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 oneRACDisposableobject
  • Normally, the subscription status is automatically removed after signal sends complete or errorRACDisposableIt 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)nextThe event will be sent before it arrivesnextThe 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-effectThe things

Use skills

  • RAC(self.passwordTextField, backgroundColor) = xxxsignalThe result of the xxxSignal event is assigned to the backgroundColor of the textField

doubt

  1. RACSignalthecreateSignalThe method declaration says that after the signal is created, once there are subscribers, thencreateSignalFor 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