[[152667]] Preface Due to time constraints, I have only updated this much for now. I will continue to update this article "The fastest way to get started with ReactiveCocoa: Advanced Edition" in the future. At present, I have only briefly introduced some core methods of RAC. In the future, I will need to add practical development of MVVM+ReactiveCocoa. If you like my article, you can follow me or visit Xiaoma Ge to learn about our iOS training courses. 1.Introduction to common operating methods of ReactiveCocoa. 1.1 ReactiveCocoa Operation Notes <br /> All signals (RACSignal) can be operated and processed, because all operation methods are defined in RACStream.h, so as long as you inherit RACStream, you will have the operation processing method. 1.2 ReactiveCocoa operation concept <br /> It uses the Hook concept, which is a technology used to change the execution result of an API (application programming interface: method). Hook usage: Technology for intercepting API calls. Hook principle: Before calling an API to return the result each time, execute your own method to change the output of the result. 1.3 ReactiveCocoa core method bind The core method of ReactiveCocoa operation is bind, and the core development method in RAC is also binding. The previous development method was assignment, and when developing with RAC, the focus should be on binding, that is, when creating an object, you can bind what you want to do later, instead of waiting for assignment before doing things. For example, to display data on a control, you previously had to override the setModel method of the control. With RAC, you can bind the data when you first create the control. The bind method is rarely used in development. It is a low-level method in RAC. RAC has encapsulated many other useful methods. The bottom layer calls bind, which is simpler to use than bind. A brief introduction and usage of the bind method. -
-
-
- [_textField.rac_textSignal subscribeNext:^(id x) {
-
- NSLog(@ "output:%@" , x);
-
- }];
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [[_textField.rac_textSignal bind:^RACStreamBindBlock{
-
-
-
-
- return ^RACStream *(id value, BOOL *stop){
-
-
-
-
-
-
- return [RACReturnSignal return :[NSString stringWithFormat:@ "Output:%@" ,value]];
- };
-
- }] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
-
- }];
1.4ReactiveCocoa operation method mapping (flattenMap, Map) flattenMap, Map is used to map the source signal content into new content. FlattenMap is easy to use -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
-
-
-
-
-
-
- return [RACReturnSignal return :[NSString stringWithFormat:@ "Output:%@" ,value]];
-
- }] subscribeNext:^(id x) {
-
-
-
- NSLog(@ "%@" ,x);
-
- }];
-
- Map is easy to use:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [[_textField.rac_textSignal map:^id(id value) {
-
-
- return [NSString stringWithFormat:@ "Output:%@" ,value];
- }] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
Difference between FlatternMap and Map 1.Block return signal in FlatternMap. 2.Block in Map returns an object. 3. During development, if the value emitted by a signal is not a signal, Map is generally used for mapping 4. During development, if the value emitted by a signal is a signal, FlatternMap is generally used for mapping. Summary: signalOfsignals uses FlatternMap. -
- RACSubject *signalOfsignals = [RACSubject subject];
- RACSubject *signal = [RACSubject subject];
-
- [[signalOfsignals flattenMap:^RACStream *(id value) {
-
-
-
- return value;
-
- }] subscribeNext:^(id x) {
-
-
-
-
- NSLog(@ "%@aaa" ,x);
- }];
-
-
- [signalOfsignals sendNext:signal];
-
-
- [signal sendNext: @1 ];
1.5 Combination of ReactiveCocoa operation methods. * `concat`: concatenate signals in a certain order. When multiple signals are sent, the signals are received in order. - RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
-
- [subscriber sendCompleted];
-
- return nil;
- }];
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @2 ];
-
- return nil;
- }];
-
-
- RACSignal *concatSignal = [signalA concat:signalB];
-
-
-
-
-
- [concatSignal subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
-
- }];
-
-
-
-
-
-
-
-
-
-
- * `then`: used to connect two signals. When the first signal is completed, the signal returned by then will be connected.
-
- ```
-
-
-
- [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
- [subscriber sendCompleted];
- return nil;
- }] then:^RACSignal *{
- return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
- [subscriber sendNext: @2 ];
- return nil;
- }];
- }] subscribeNext:^(id x) {
-
-
- NSLog(@ "%@" ,x);
- }];
- ```
- * `merge`: Merge multiple signals into one signal, which will be called when any signal has a new value
-
-
-
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
-
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @2 ];
-
- return nil;
- }];
-
-
- RACSignal *mergeSignal = [signalA merge:signalB];
-
- [mergeSignal subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
-
- }];
-
-
-
-
-
-
-
- * `zipWith`: compress two signals into one signal. The next event of the compressed stream will be triggered only when the two signals send out the signal content at the same time and the contents of the two signals are merged into a tuple.
-
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
-
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @2 ];
-
- return nil;
- }];
-
-
- RACSignal *zipSignal = [signalA zipWith:signalB];
-
- [zipSignal subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
-
-
-
-
- * `combineLatest`: Combine multiple signals and get the latest value of each signal. Each combined signal must have at least one sendNext to trigger the combined signal.
-
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @2 ];
-
- return nil;
- }];
-
-
- RACSignal *combineSignal = [signalA combineLatestWith:signalB];
-
- [combineSignal subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
-
-
-
-
- * `reduce` aggregation: The content of the signal emitted is a tuple, and the value of the signal emitted tuple is aggregated into one value
-
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @2 ];
-
- return nil;
- }];
-
-
-
-
-
-
- RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA,signalB] reduce:^id(NSNumber *num1 ,NSNumber *num2){
-
- return [NSString stringWithFormat:@ "%@ %@" ,num1,num2];
-
- }];
-
- [reduceSignal subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
-
-
-
- 1.6 ReactiveCocoa operation method filtering.
-
- filter: Filter signals, which can be used to obtain signals that meet the conditions.
-
-
-
- [_textField.rac_textSignal filter:^BOOL(NSString *value) {
- return value.length > 3 ;
- }];
-
- ignore: Ignore signals with certain values.
-
-
- [[_textField.rac_textSignal ignore:@ "1" ] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
- distinctUntilChanged: A signal is emitted when there is a significant change between the previous value and the current value, otherwise it will be ignored.
-
-
-
- [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
take: Take N signals from the beginning -
- RACSubject *signal = [RACSubject subject];
-
-
- [[signal take: 1 ] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
-
- [signal sendNext: @1 ];
-
- [signal sendNext: @2 ];
-
- takeLast: Take the signal of ***N times. The prerequisite is that the subscriber must call completion, because only when it is completed, we know how many signals there are in total.
-
-
- RACSubject *signal = [RACSubject subject];
-
-
- [[signal takeLast: 1 ] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
-
- [signal sendNext: @1 ];
-
- [signal sendNext: @2 ];
-
- [signal sendCompleted];
-
- takeUntil:(RACSignal *): Get the signal until the signal is executed
-
-
- [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
-
- skip: (NSUInteger): skip several signals and do not accept them.
-
-
- [[_textField.rac_textSignal skip: 1 ] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
- switchToLatest: is used for signalOfSignals (signal of signal). Sometimes the signal will also send a signal. In signalOfSignals, we will get the *** signal sent by signalOfSignals.
-
- RACSubject *signalOfSignals = [RACSubject subject];
- RACSubject *signal = [RACSubject subject];
- [signalOfSignals sendNext:signal];
- [signal sendNext: @1 ];
-
-
-
- [signalOfSignals.switchToLatest subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
1.7 The order of ReactiveCocoa operation methods. - doNext: Before executing Next, this Block will be executed first
-
- doCompleted: This Block will be executed before executing sendCompleted
-
- [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
- [subscriber sendNext: @1 ];
- [subscriber sendCompleted];
- return nil;
- }] doNext:^(id x) {
-
- NSLog(@ "doNext" );
- }] doCompleted:^{
-
- NSLog(@ "doCompleted" );
-
- }] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
1.8 ReactiveCocoa operation methods: threads. deliverOn: Content delivery is switched to the specified thread, and the side effects are in the original thread. The code in the block when creating the signal is called the side effect. subscribeOn: Both content delivery and side effects will be switched to the specified thread. 1.9 Time of ReactiveCocoa operation methods. - timeout: Timeout allows a signal to automatically report an error after a certain period of time.
-
- RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
- return nil;
- }] timeout: 1 onScheduler:[RACScheduler currentScheduler]];
-
- [signal subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- } error:^(NSError *error) {
-
- NSLog(@ "%@" ,error);
- }];
-
- Interval timing: send a signal at intervals
-
- [[RACSignal interval: 1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
-
- delay delays sending next.
-
- RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
- return nil;
- }] delay: 2 ] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
1.9 Duplication of ReactiveCocoa operation methods. retry: As long as it fails, the block in the creation signal will be re-executed until it succeeds. - ```
- __block int i = 0 ;
- [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
- if (i == 10 ) {
- [subscriber sendNext: @1 ];
- } else {
- NSLog(@ "Received error" );
- [subscriber sendError:nil];
- }
- i++;
-
- return nil;
-
- }] retry] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
-
- } error:^(NSError *error) {
-
- }];
- ```
-
- replay: When a signal is subscribed to multiple times, the content is played repeatedly
-
- ```
- RACSignal signal = [[RACSignal createSignal:^RACDisposable (id<RACSubscriber> subscriber) {
-
- [subscriber sendNext: @1 ];
- [subscriber sendNext: @2 ];
-
- return nil;
- }] replay];
-
- [signal subscribeNext:^(id x) {
-
- NSLog(@ "*** subscribers %@" , x);
-
- }];
-
- [signal subscribeNext:^(id x) {
-
- NSLog(@ "Second subscriber %@" , x);
-
- }];
-
- ```
-
- Throttle: When a signal is sent frequently, throttling can be used. The signal content is not sent for a period of time, and after a period of time, the latest content of the signal is obtained and sent.
-
- RACSubject *signal = [RACSubject subject];
-
- _signal = signal;
-
-
- [[signal throttle: 1 ] subscribeNext:^(id x) {
-
- NSLog(@ "%@" ,x);
- }];
2. Introduce the MVVM architectural idea. 2.1 Why programs need architecture: To facilitate programmers to develop and maintain code. 2.2 Common architectural ideas: MVC M: Model V: View C: Controller MVVM M: Model V: View + Controller VM: View Model MVCS M: Model V: View C: Controller C: Service Class VIPER V: View I: Interactor P: Presenter E: Entity R: Router PS:VIPER Architecture Idea 2.3 Introduction to MVVM Model (M): holds view data. View + Controller (V): What to display + How to display it View Model (VM): handles the business logic of the display, including button clicks, data requests and parsing, etc. 3.ReactiveCocoa + MVVM Practice 1: Login Interface 3.1 Requirements + Analysis + Steps /* Requirement: 1. Monitor the contents of the two text boxes, and allow the button to be clicked only if there is content 2.Default login request. Use MVVM: to implement all business logic analysis of the previous interface: 1. All business logic of the previous interface is handed over to the controller for processing 2. In the MVVM architecture, all the controller's business is moved to the VM model, that is, each controller corresponds to a VM model. Steps: 1. Create the LoginViewModel class to handle the business logic of the login interface. 2. This class should store account information and create an Account model 3. LoginViewModel should save the account information Account model. 4. How to monitor changes in account numbers and passwords in the Account model at all times? 5. In non-RAC development, people are used to assigning values. In RAC development, you need to change your development mindset from assigning values to binding. You can bind the attributes in the Account model at the beginning of initialization without overriding the set method. 6. Every time the value of the Account model changes, it is necessary to determine whether the button can be clicked, process it in the VM model, and provide a signal to the outside world whether the button can be clicked. 7. This login signal needs to determine whether the account and password in Account have values, use KVO to monitor the changes of these two values, and aggregate them into a login signal. 8. The click of the monitoring button is handled by the VM. A RACCommand should be declared for the VM to handle the login business logic. 9. Execute the command and package the data into a signal to pass it out 10. Data transmission of signals in monitoring commands 11.Monitor the execution time of the command */ 3.2 Controller code - @interface ViewController ()
-
- @property (nonatomic, strong) LoginViewModel *loginViewModel;
-
- @property (weak, nonatomic) IBOutlet UITextField *accountField;
- @property (weak, nonatomic) IBOutlet UITextField *pwdField;
-
- @property (weak, nonatomic) IBOutlet UIButton *loginBtn;
-
-
- @end
-
- - (LoginViewModel *)loginViewModel
- {
- if (_loginViewModel == nil) {
-
- _loginViewModel = [[LoginViewModel alloc] init];
- }
- return _loginViewModel;
- }
-
-
- - ( void )bindModel
- {
-
-
- RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
- RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;
-
-
- RAC(self.loginBtn,enabled) = self.loginViewModel.enableLoginSignal;
-
-
- [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
-
-
- [self.loginViewModel.LoginCommand execute:nil];
- }];
- }
3.3 VM Code - @interface LoginViewModel : NSObject
-
- @property (nonatomic, strong) Account *account;
-
-
-
- @property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;
-
- @property (nonatomic, strong, readonly) RACCommand *LoginCommand;
-
- @end
-
- @implementation LoginViewModel
- - (Account *)account
- {
- if (_account == nil) {
- _account = [[Account alloc] init];
- }
- return _account;
- }
- - (instancetype)init
- {
- if (self = [ super init]) {
- [self initialBind];
- }
- return self;
- }
-
-
-
- - ( void )initialBind
- {
-
- _enableLoginSignal = [RACSignal combineLatest:@[RACObserve(self.account, account),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
-
- return @(account.length && pwd.length);
-
- }];
-
-
- _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
-
- NSLog(@ "Clicked to log in" );
- return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-
- [subscriber sendNext:@ "Login successful" ];
-
-
- [subscriber sendCompleted];
- });
-
- return nil;
- }];
- }];
-
-
- [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
-
- if ([x isEqualToString:@ "Login successful" ]) {
- NSLog(@ "Login successful" );
- }
- }];
-
-
- [[_LoginCommand.executing skip: 1 ] subscribeNext:^(id x) {
- if ([x isEqualToNumber:@(YES)]) {
-
-
-
- [MBProgressHUD showMessage:@ "Logging in..." ];
-
-
- } else
- {
-
-
- [MBProgressHUD hideHUD];
- }
- }];
- }
4.ReactiveCocoa + MVVM Practice 2: Network Request Data 4.1 Interface: Here I would like to introduce a free network data interface, Douban. It can be used to practice some small demos of network requests. 4.2 Requirements + Analysis + Steps /* Requirement: Request Douban book information, url: https://api.douban.com/v2/book/search?q=Basic Analysis: The request is the same and is managed by the VM model step: 1. The controller provides a view model (requesViewModel) to handle the business logic of the interface 2.VM provides a command to process the request business logic 3. In the block that creates the command, the request will be packaged into a signal, and when the request is successful, the data will be passed out. 4. If the data is requested successfully, the dictionary should be converted into a model and saved in the view model. The controller can directly obtain it from the view model when it wants to use it. 5. Assuming that the controller wants to display content to the tableView, directly let the view model become the data source of the tableView and leave all business logic to the view model. In this way, the controller code will be very small. */ 4.3 Controller Code - @interface ViewController ()
-
- @property (nonatomic, weak) UITableView *tableView;
-
- @property (nonatomic, strong) RequestViewModel *requesViewModel;
-
-
- @end
-
- @implementation ViewController
- - (RequestViewModel *)requesViewModel
- {
- if (_requesViewModel == nil) {
- _requesViewModel = [[RequestViewModel alloc] init];
- }
- return _requesViewModel;
- }
-
- - ( void )viewDidLoad {
- [ super viewDidLoad];
-
-
-
- UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
- tableView.dataSource = self.requesViewModel;
- self.requesViewModel.tableView = tableView;
- [self.view addSubview:tableView];
-
-
- [self.requesViewModel.reuqesCommand execute:nil];
-
- }
-
-
- @end
4.4 View Model (VM) Code - @interface RequestViewModel : NSObject<UITableViewDataSource>
-
-
-
- @property (nonatomic, strong, readonly) RACCommand *reuqesCommand;
-
-
- @property (nonatomic, strong, readonly) NSArray *models;
-
-
- @property (nonatomic, weak) UITableView *tableView;
-
- @end
-
- @implementation RequestViewModel
-
- - (instancetype)init
- {
- if (self = [ super init]) {
-
- [self initialBind];
- }
- return self;
- }
-
-
- - ( void )initialBind
- {
- _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
-
- RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
-
-
- NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
- parameters[@ "q" ] = @ "base" ;
-
-
- [[AFHTTPRequestOperationManager manager] GET:@ "https://api.douban.com/v2/book/search" parameters:parameters success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
- NSLog(@ "%@" ,responseObject);
-
-
-
- [subscriber sendNext:responseObject];
-
- [subscriber sendCompleted];
-
-
- } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
-
-
- }];
-
- return nil;
- }];
-
-
-
- return [requestSignal map:^id(NSDictionary *value) {
- NSMutableArray *dictArr = value[@ "books" ];
-
-
- NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
-
- return [Book bookWithDict:value];
- }]array];
-
- return modelArr;
- }];
-
- }];
-
-
- [_reuqesCommand.executionSignals.switchToLatest subscribeNext:^(NSArray *x) {
-
-
- _models = x;
-
-
- [self.tableView reloadData];
-
- }];
- }
-
- #pragma mark - UITableViewDataSource
-
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return self.models.count;
- }
-
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *ID = @ "cell" ;
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
- if (cell == nil) {
-
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
- }
-
- Book *book = self.models[indexPath.row];
- cell.detailTextLabel.text = book.subtitle;
- cell.textLabel.text = book.title;
-
- return cell;
- }
-
- @end
|