The fastest way to get started with ReactiveCocoa: Advanced Edition

The fastest way to get started with ReactiveCocoa: Advanced Edition

[[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.

  1.   // Suppose you want to monitor the content of the text box, and each time you output a result, add a paragraph of text "Output:" to the content of the text box  
  2.  
  3. // Method 1: After returning the result, concatenate.  
  4. [_textField.rac_textSignal subscribeNext:^(id x) {
  5.  
  6. NSLog(@ "output:%@" , x);
  7.  
  8. }];
  9.  
  10. // Method 2: Before returning the result, concatenate and use the bind method in RAC to process it.  
  11. // bind method parameters: need to pass in a block parameter whose return value is RACStreamBindBlock  
  12. //RACStreamBindBlock is a block type, the return value is a signal, the parameters are (value, stop), so the block return value of the parameter is also a block.  
  13.  
  14. //RACStreamBindBlock:  
  15. // Parameter 1 (value): indicates the original value of the received signal, which has not been processed yet  
  16. // Parameter 2 (*stop): used to control the binding Block. If *stop = yes, the binding will end.  
  17. // Return value: signal, process it and return it through this signal. Generally, RACReturnSignal is used. The header file RACReturnSignal.h needs to be manually imported.  
  18.  
  19. // Steps to use the bind method:  
  20. // 1. Pass in a block that returns a value RACStreamBindBlock.  
  21. // 2. Describe a bindBlock of type RACStreamBindBlock as the return value of block.  
  22. // 3. Describe a signal that returns a result as the return value of bindBlock.  
  23. // Note: Process the signal result in bindBlock.  
  24.  
  25. // Bottom layer implementation:  
  26. // 1. The source signal calls bind, which will recreate a bound signal.  
  27. // 2. When the binding signal is subscribed, the didSubscribe in the binding signal will be called to generate a bindingBlock.  
  28. // 3. When the source signal has content, it will pass the content to the bindingBlock for processing and call bindingBlock(value,stop)  
  29. // 4. Calling bindingBlock(value,stop) will return a signal (RACReturnSignal) indicating that content processing is complete.  
  30. // 5. Subscribe to RACReturnSignal, and you will get the subscriber of the bound signal and send out the processed signal content.  
  31.  
  32. // Note: Different subscribers save different nextBlocks. When reading the source code, be sure to see clearly which subscriber it is.  
  33.  
  34. [[_textField.rac_textSignal bind:^RACStreamBindBlock{
  35.  
  36. // When to call:  
  37. //Block function: indicates that a signal is bound.  
  38.  
  39. return ^RACStream *(id value, BOOL *stop){
  40.  
  41. // When to call the block: When the signal has a new value, it will come to this block.  
  42.  
  43. //Block function: process the return value  
  44.  
  45. // Do the processing and return it through the signal.  
  46. return [RACReturnSignal return :[NSString stringWithFormat:@ "Output:%@" ,value]];
  47. };
  48.  
  49. }] subscribeNext:^(id x) {
  50.  
  51. NSLog(@ "%@" ,x);
  52.  
  53. }];

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

  1.   // Listen for changes to the text box's content and remap the structure to a new value.  
  2.  
  3. // flattenMap function: maps the content of the source signal into a new signal, which can be of any type.  
  4.  
  5. // Steps to use flattenMap:  
  6. // 1. Pass in a block, the block type is the return value RACStream, the parameter value  
  7. // 2. The parameter value is the content of the source signal. Get the content of the source signal for processing  
  8. // 3. Package it into RACReturnSignal signal and return it.  
  9.  
  10. // flattenMap underlying implementation:  
  11. // 0.flattenMap internally calls the bind method. The return value of block in flattenMap will be used as the return value of bindBlock in bind.  
  12. // 1. When subscribing to a binding signal, a bindBlock is generated.  
  13. // 2. When the source signal sends content, bindBlock(value, *stop) will be called  
  14. // 3. Call bindBlock, and the flattenMap block will be called internally. The function of the flattenMap block is to package the processed data into a signal.  
  15. // 4. The returned signal will eventually be used as the return signal in bindBlock, as the return signal of bindBlock.  
  16. // 5. Subscribe to the return signal of bindBlock, you will get the subscriber of the bound signal and send out the processed signal content.  
  17.  
  18. [[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
  19.  
  20. // When the block is called: When the source signal is emitted, this block will be called.  
  21.  
  22. //Block function: change the content of the source signal.  
  23.  
  24. // Return value: the content of the bound signal.  
  25. return [RACReturnSignal return :[NSString stringWithFormat:@ "Output:%@" ,value]];
  26.  
  27. }] subscribeNext:^(id x) {
  28.  
  29. //Subscribe to the bound signal. Whenever the source signal sends content and completes processing, this block will be called.  
  30.  
  31. NSLog(@ "%@" ,x);
  32.  
  33. }];
  34.  
  35. Map is easy to use:
  36.  
  37. // Listen for changes to the text box's content and remap the structure to a new value.  
  38.  
  39. // Map function: map the value of the source signal to a new value  
  40.  
  41. // Map usage steps:  
  42. // 1. Pass in a block, the type is the return object, the parameter is value  
  43. // 2.value is the content of the source signal, directly get the content of the source signal for processing  
  44. // 3. Just return the processed content directly without packaging it into a signal. The returned value is the mapped value.  
  45.  
  46. // Map underlying implementation:  
  47. // 0. The underlying layer of Map actually calls flatternMap, and the returned value in the block in Map will be used as the value in the block in flatternMap.  
  48. // 1. When subscribing to a binding signal, a bindBlock is generated.  
  49. // 3. When the source signal sends content, bindBlock(value, *stop) will be called  
  50. // 4. Call bindBlock, and the flattenMap block will be called internally  
  51. // 5. The flattenMap block will call the block in Map and wrap the content returned by the block in Map into a returned signal.  
  52. // 5. The returned signal will eventually be used as the return signal in bindBlock, as the return signal of bindBlock.  
  53. // 6. Subscribe to the return signal of bindBlock, you will get the subscriber of the bound signal and send out the processed signal content.  
  54.  
  55. [[_textField.rac_textSignal map:^id(id value) {
  56. // When the source signal is sent, this block will be called to modify the content of the source signal  
  57. // Return value: the content after processing the source signal.  
  58. return [NSString stringWithFormat:@ "Output:%@" ,value];
  59. }] subscribeNext:^(id x) {
  60.  
  61. NSLog(@ "%@" ,x);
  62. }];

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.

  1.   // Create a signal in signal  
  2. RACSubject *signalOfsignals = [RACSubject subject];
  3. RACSubject *signal = [RACSubject subject];
  4.  
  5. [[signalOfsignals flattenMap:^RACStream *(id value) {
  6.  
  7. // Called only when signalsOfsignals emits a signal  
  8.  
  9. return value;
  10.  
  11. }] subscribeNext:^(id x) {
  12.  
  13. // This function will only be called when the signal of signalOfsignals sends a signal, because it subscribes internally to the signal returned in bindBlock, which is the signal returned by flattenMap.  
  14. // That is, the signal returned by flattenMap will be called.  
  15.  
  16. NSLog(@ "%@aaa" ,x);
  17. }];
  18.  
  19. //Signal of signal sends signal  
  20. [signalOfsignals sendNext:signal];
  21.  
  22. // Signal sending content  
  23. [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.

  1. RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  2.  
  3. [subscriber sendNext: @1 ];
  4.  
  5. [subscriber sendCompleted];
  6.  
  7. return nil;
  8. }];
  9. RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  10.  
  11. [subscriber sendNext: @2 ];
  12.  
  13. return nil;
  14. }];
  15.  
  16. // After splicing signalA to signalB, signalB will be activated only after signalA is sent.  
  17. RACSignal *concatSignal = [signalA concat:signalB];
  18.  
  19. // In the future, you only need to deal with splicing signal development.  
  20. // Subscribe to the concatenated signal, no need to subscribe to signalA and signalB separately  
  21. // Will be subscribed automatically internally.  
  22. // Note: The first signal must be sent before the second signal will be activated  
  23. [concatSignal subscribeNext:^(id x) {
  24.  
  25. NSLog(@ "%@" ,x);
  26.  
  27. }];
  28.  
  29. // concat underlying implementation:  
  30. // 1. When the splicing signal is subscribed, the splicing signal's didSubscribe will be called  
  31. // 2. In didSubscribe, the first source signal (signalA) will be subscribed first  
  32. // 3. Will execute the didSubscribe of the first source signal (signalA)  
  33. // 4. When the value is sent in the didSubscribe of the first source signal (signalA), the nextBlock of the subscriber of the first source signal (signalA) will be called, and the value will be sent out through the subscriber of the spliced ​​signal.  
  34. // 5. When the first source signal (signalA) is sent in didSubscribe, the completedBlock of the first source signal (signalA) subscriber will be called, and the subscription to the second source signal (signalB) will be activated at this time (signalB).  
  35. // 6. Subscribe to the second source signal (signalB) and execute the didSubscribe of the second source signal (signalB)  
  36. // 7. The second source signal (signalA) sends a value in didSubscribe, and the value will be sent out through the subscriber of the spliced ​​signal.  
  37.  
  38. * `then`: used to connect two signals. When the first signal is completed, the signal returned by then will be connected.
  39.  
  40. ```
  41. // then: used to connect two signals. When the first signal is completed, the signal returned by then will be connected  
  42. // Note that when using then, the previous value of the signal will be ignored.  
  43. // Bottom-level implementation: 1. Filter out the value sent by the previous signal. 2. Use concat to connect the signal returned by then  
  44. [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  45.  
  46. [subscriber sendNext: @1 ];
  47. [subscriber sendCompleted];
  48. return nil;
  49. }] then:^RACSignal *{
  50. return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  51. [subscriber sendNext: @2 ];
  52. return nil;
  53. }];
  54. }] subscribeNext:^(id x) {
  55.  
  56. // Can only receive the value of the second signal, that is, the value of the then return signal  
  57. NSLog(@ "%@" ,x);
  58. }];
  59. ```
  60. * `merge`: Merge multiple signals into one signal, which will be called when any signal has a new value
  61.  
  62. // merge: merge multiple signals into one signal  
  63. // Create multiple signals  
  64. RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  65.  
  66. [subscriber sendNext: @1 ];
  67.  
  68.  
  69. return nil;
  70. }];
  71.  
  72. RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  73.  
  74. [subscriber sendNext: @2 ];
  75.  
  76. return nil;
  77. }];
  78.  
  79. //Merge signals, any signal that sends data can be monitored.  
  80. RACSignal *mergeSignal = [signalA merge:signalB];
  81.  
  82. [mergeSignal subscribeNext:^(id x) {
  83.  
  84. NSLog(@ "%@" ,x);
  85.  
  86. }];
  87.  
  88. // Bottom layer implementation:  
  89. // 1. When the merge signal is subscribed, all signals will be traversed and emitted.  
  90. // 2. Every time a signal is sent, this signal will be subscribed  
  91. // 3. That is, once the merged signal is subscribed, all the signals in it will be subscribed.  
  92. // 4. As long as a signal is sent, it will be monitored.  
  93.  
  94. * `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.
  95.  
  96. RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  97.  
  98. [subscriber sendNext: @1 ];
  99.  
  100.  
  101. return nil;
  102. }];
  103.  
  104. RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  105.  
  106. [subscriber sendNext: @2 ];
  107.  
  108. return nil;
  109. }];
  110.  
  111. //Compress signal A, signal B  
  112. RACSignal *zipSignal = [signalA zipWith:signalB];
  113.  
  114. [zipSignal subscribeNext:^(id x) {
  115.  
  116. NSLog(@ "%@" ,x);
  117. }];
  118.  
  119. // Bottom layer implementation:  
  120. // 1. Define the compressed signal, and signalA and signalB will be automatically subscribed internally  
  121. // 2. Whenever signalA or signalB sends a signal, it will determine whether signalA or signalB has sent a signal. If so, the most recently sent signal will be packaged into a tuple and sent out.  
  122.  
  123. * `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.
  124.  
  125. RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  126.  
  127. [subscriber sendNext: @1 ];
  128.  
  129. return nil;
  130. }];
  131.  
  132. RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  133.  
  134. [subscriber sendNext: @2 ];
  135.  
  136. return nil;
  137. }];
  138.  
  139. // Combine two signals into one signal, just like zip, no difference  
  140. RACSignal *combineSignal = [signalA combineLatestWith:signalB];
  141.  
  142. [combineSignal subscribeNext:^(id x) {
  143.  
  144. NSLog(@ "%@" ,x);
  145. }];
  146.  
  147. // Bottom layer implementation:  
  148. // 1. When the combined signal is subscribed, signalA and signalB will be automatically subscribed internally. Both signals must send content before they will be triggered.  
  149. // 2. And combine the two signals into a tuple and send it out.  
  150.  
  151. * `reduce` aggregation: The content of the signal emitted is a tuple, and the value of the signal emitted tuple is aggregated into one value
  152.  
  153. RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  154.  
  155. [subscriber sendNext: @1 ];
  156.  
  157. return nil;
  158. }];
  159.  
  160. RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  161.  
  162. [subscriber sendNext: @2 ];
  163.  
  164. return nil;
  165. }];
  166.  
  167. // Aggregation  
  168. // Common usage, (combine first, then aggregate). combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock  
  169. // Introduction to block in reduce:  
  170. // The parameters in reduceblcok, there are as many parameters as there are signal combinations, and each parameter is the content of the previous signal.  
  171. // The return value of reduceblcok: the content after the aggregation signal.  
  172. RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA,signalB] reduce:^id(NSNumber *num1 ,NSNumber *num2){
  173.  
  174. return [NSString stringWithFormat:@ "%@ %@" ,num1,num2];
  175.  
  176. }];
  177.  
  178. [reduceSignal subscribeNext:^(id x) {
  179.  
  180. NSLog(@ "%@" ,x);
  181. }];
  182.  
  183. // Bottom layer implementation:  
  184. // 1. Subscribe to the aggregation signal. Every time there is content, reduceblcok will be executed to convert the signal content into the value returned by reduceblcok.  
  185.  
  186. 1.6 ReactiveCocoa operation method filtering.
  187.  
  188. filter: Filter signals, which can be used to obtain signals that meet the conditions.
  189.  
  190. // Filter:  
  191. // Every time a signal is sent, the filter condition will be judged first.  
  192. [_textField.rac_textSignal filter:^BOOL(NSString *value) {
  193. return value.length > 3 ;
  194. }];
  195.  
  196. ignore: Ignore signals with certain values.
  197.  
  198. // Call filter internally to ignore the value of ignore  
  199. [[_textField.rac_textSignal ignore:@ "1" ] subscribeNext:^(id x) {
  200.  
  201. NSLog(@ "%@" ,x);
  202. }];
  203.  
  204. distinctUntilChanged: A signal is emitted when there is a significant change between the previous value and the current value, otherwise it will be ignored.
  205.  
  206. // Filter, when the previous value is different from the current value, the content will be emitted.  
  207. // In development, refresh UI is often used, and refresh is only required when the data is different twice  
  208. [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
  209.  
  210. NSLog(@ "%@" ,x);
  211. }];

take: Take N signals from the beginning

  1.   // 1. Create a signal  
  2. RACSubject *signal = [RACSubject subject];
  3.  
  4. // 2. Process signals and subscribe to signals  
  5. [[signal take: 1 ] subscribeNext:^(id x) {
  6.  
  7. NSLog(@ "%@" ,x);
  8. }];
  9.  
  10. // 3. Send signal  
  11. [signal sendNext: @1 ];
  12.  
  13. [signal sendNext: @2 ];
  14.  
  15. 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.
  16.  
  17. // 1. Create a signal  
  18. RACSubject *signal = [RACSubject subject];
  19.  
  20. // 2. Process signals and subscribe to signals  
  21. [[signal takeLast: 1 ] subscribeNext:^(id x) {
  22.  
  23. NSLog(@ "%@" ,x);
  24. }];
  25.  
  26. // 3. Send signal  
  27. [signal sendNext: @1 ];
  28.  
  29. [signal sendNext: @2 ];
  30.  
  31. [signal sendCompleted];
  32.  
  33. takeUntil:(RACSignal *): Get the signal until the signal is executed
  34.  
  35. // Listen for changes in the text box until the current object is destroyed  
  36. [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
  37.  
  38. skip: (NSUInteger): skip several signals and do not accept them.
  39.  
  40. // indicates that the input is the ***th time, it will not be monitored, and the signal sent the ***th time will be skipped  
  41. [[_textField.rac_textSignal skip: 1 ] subscribeNext:^(id x) {
  42.  
  43. NSLog(@ "%@" ,x);
  44. }];
  45.  
  46. 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.
  47.  
  48. RACSubject *signalOfSignals = [RACSubject subject];
  49. RACSubject *signal = [RACSubject subject];
  50. [signalOfSignals sendNext:signal];
  51. [signal sendNext: @1 ];
  52.  
  53. // Get the most recently emitted signal among the signals and subscribe to the most recently emitted signal.  
  54. // Note switchToLatest: can only be used for signals within signals  
  55. [signalOfSignals.switchToLatest subscribeNext:^(id x) {
  56.  
  57. NSLog(@ "%@" ,x);
  58. }];

1.7 The order of ReactiveCocoa operation methods.

  1. doNext: Before executing Next, this Block will be executed first
  2.  
  3. doCompleted: This Block will be executed before executing sendCompleted
  4.  
  5. [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  6. [subscriber sendNext: @1 ];
  7. [subscriber sendCompleted];
  8. return nil;
  9. }] doNext:^(id x) {
  10. // This Block will be called before executing [subscriber sendNext:@1];  
  11. NSLog(@ "doNext" );
  12. }] doCompleted:^{
  13. // This Block will be called before executing [subscriber sendCompleted];  
  14. NSLog(@ "doCompleted" );
  15.  
  16. }] subscribeNext:^(id x) {
  17.  
  18. NSLog(@ "%@" ,x);
  19. }];

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.

  1. timeout: Timeout allows a signal to automatically report an error after a certain period of time.
  2.  
  3. RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  4. return nil;
  5. }] timeout: 1 onScheduler:[RACScheduler currentScheduler]];
  6.  
  7. [signal subscribeNext:^(id x) {
  8.  
  9. NSLog(@ "%@" ,x);
  10. } error:^(NSError *error) {
  11. // Will be called automatically after 1 second  
  12. NSLog(@ "%@" ,error);
  13. }];
  14.  
  15. Interval timing: send a signal at intervals
  16.  
  17. [[RACSignal interval: 1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
  18.  
  19. NSLog(@ "%@" ,x);
  20. }];
  21.  
  22. delay delays sending next.
  23.  
  24. RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  25.  
  26. [subscriber sendNext: @1 ];
  27. return nil;
  28. }] delay: 2 ] subscribeNext:^(id x) {
  29.  
  30. NSLog(@ "%@" ,x);
  31. }];

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.

  1. ```
  2. __block int i = 0 ;
  3. [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  4.  
  5. if (i == 10 ) {
  6. [subscriber sendNext: @1 ];
  7. } else {
  8. NSLog(@ "Received error" );
  9. [subscriber sendError:nil];
  10. }
  11. i++;
  12.  
  13. return nil;
  14.  
  15. }] retry] subscribeNext:^(id x) {
  16.  
  17. NSLog(@ "%@" ,x);
  18.  
  19. } error:^(NSError *error) {
  20.  
  21. }];
  22. ```
  23.  
  24. replay: When a signal is subscribed to multiple times, the content is played repeatedly
  25.  
  26. ```
  27. RACSignal signal = [[RACSignal createSignal:^RACDisposable (id<RACSubscriber> subscriber) {
  28.  
  29. [subscriber sendNext: @1 ];
  30. [subscriber sendNext: @2 ];
  31.  
  32. return nil;
  33. }] replay];
  34.  
  35. [signal subscribeNext:^(id x) {
  36.  
  37. NSLog(@ "*** subscribers %@" , x);
  38.  
  39. }];
  40.  
  41. [signal subscribeNext:^(id x) {
  42.  
  43. NSLog(@ "Second subscriber %@" , x);
  44.  
  45. }];
  46.  
  47. ```
  48.  
  49. 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.
  50.  
  51. RACSubject *signal = [RACSubject subject];
  52.  
  53. _signal = signal;
  54.  
  55. //Throttling, within a certain period of time (1 second), do not receive any signal content, after this time (1 second) get the signal content sent by *** and send it out.  
  56. [[signal throttle: 1 ] subscribeNext:^(id x) {
  57.  
  58. NSLog(@ "%@" ,x);
  59. }];

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

  1. @interface ViewController ()
  2.  
  3. @property (nonatomic, strong) LoginViewModel *loginViewModel;
  4.  
  5. @property (weak, nonatomic) IBOutlet UITextField *accountField;
  6. @property (weak, nonatomic) IBOutlet UITextField *pwdField;
  7.  
  8. @property (weak, nonatomic) IBOutlet UIButton *loginBtn;
  9.  
  10.  
  11. @end  
  12.  
  13. - (LoginViewModel *)loginViewModel
  14. {
  15. if (_loginViewModel == nil) {
  16.  
  17. _loginViewModel = [[LoginViewModel alloc] init];
  18. }
  19. return _loginViewModel;
  20. }
  21.  
  22. // View model binding  
  23. - ( void )bindModel
  24. {
  25. // Bind signals to model attributes  
  26. // As long as the account text box changes, the account will be assigned a value  
  27. RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
  28. RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;
  29.  
  30. // Bind login button  
  31. RAC(self.loginBtn,enabled) = self.loginViewModel.enableLoginSignal;
  32.  
  33. // Listen for login button clicks  
  34. [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
  35.  
  36. //Execute login event  
  37. [self.loginViewModel.LoginCommand execute:nil];
  38. }];
  39. }

3.3 VM Code

  1. @interface LoginViewModel : NSObject
  2.  
  3. @property (nonatomic, strong) Account *account;
  4.  
  5.  
  6. //Signal to allow login  
  7. @property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;
  8.  
  9. @property (nonatomic, strong, readonly) RACCommand *LoginCommand;
  10.  
  11. @end  
  12.  
  13. @implementation LoginViewModel
  14. - (Account *)account
  15. {
  16. if (_account == nil) {
  17. _account = [[Account alloc] init];
  18. }
  19. return _account;
  20. }
  21. - (instancetype)init
  22. {
  23. if (self = [ super init]) {
  24. [self initialBind];
  25. }
  26. return self;
  27. }
  28.  
  29.  
  30. // Initialize binding  
  31. - ( void )initialBind
  32. {
  33. // Monitor account attribute value changes and aggregate them into one signal.  
  34. _enableLoginSignal = [RACSignal combineLatest:@[RACObserve(self.account, account),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
  35.  
  36. return @(account.length && pwd.length);
  37.  
  38. }];
  39.  
  40. // Process login business logic  
  41. _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
  42.  
  43. NSLog(@ "Clicked to log in" );
  44. return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  45.  
  46. // Simulate network latency  
  47. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  48.  
  49. [subscriber sendNext:@ "Login successful" ];
  50.  
  51. // After data transmission is completed, the command must be called to complete, otherwise the command will always be in the execution state  
  52. [subscriber sendCompleted];
  53. });
  54.  
  55. return nil;
  56. }];
  57. }];
  58.  
  59. //Monitor the data generated by the login  
  60. [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
  61.  
  62. if ([x isEqualToString:@ "Login successful" ]) {
  63. NSLog(@ "Login successful" );
  64. }
  65. }];
  66.  
  67. //Monitor login status  
  68. [[_LoginCommand.executing skip: 1 ] subscribeNext:^(id x) {
  69. if ([x isEqualToNumber:@(YES)]) {
  70.  
  71. // Logging in...  
  72. // Use mask hint  
  73. [MBProgressHUD showMessage:@ "Logging in..." ];
  74.  
  75.  
  76. } else  
  77. {
  78. // Login successful  
  79. // Hide the mask  
  80. [MBProgressHUD hideHUD];
  81. }
  82. }];
  83. }

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

  1. @interface ViewController ()
  2.  
  3. @property (nonatomic, weak) UITableView *tableView;
  4.  
  5. @property (nonatomic, strong) RequestViewModel *requesViewModel;
  6.  
  7.  
  8. @end  
  9.  
  10. @implementation ViewController
  11. - (RequestViewModel *)requesViewModel
  12. {
  13. if (_requesViewModel == nil) {
  14. _requesViewModel = [[RequestViewModel alloc] init];
  15. }
  16. return _requesViewModel;
  17. }
  18.  
  19. - ( void )viewDidLoad {
  20. [ super viewDidLoad];
  21. // Do any additional setup after loading the view, typically from a nib.  
  22.  
  23. // Create tableView  
  24. UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
  25. tableView.dataSource = self.requesViewModel;
  26. self.requesViewModel.tableView = tableView;
  27. [self.view addSubview:tableView];
  28.  
  29. // Execute the request  
  30. [self.requesViewModel.reuqesCommand execute:nil];
  31.  
  32. }
  33.  
  34.  
  35. @end  

4.4 View Model (VM) Code

  1. @interface RequestViewModel : NSObject<UITableViewDataSource>
  2.  
  3.  
  4. // Request command  
  5. @property (nonatomic, strong, readonly) RACCommand *reuqesCommand;
  6.  
  7. //Model array  
  8. @property (nonatomic, strong, readonly) NSArray *models;
  9.  
  10. //View in the controller  
  11. @property (nonatomic, weak) UITableView *tableView;
  12.  
  13. @end  
  14.  
  15. @implementation RequestViewModel
  16.  
  17. - (instancetype)init
  18. {
  19. if (self = [ super init]) {
  20.  
  21. [self initialBind];
  22. }
  23. return self;
  24. }
  25.  
  26.  
  27. - ( void )initialBind
  28. {
  29. _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
  30.  
  31. RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  32.  
  33.  
  34. NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
  35. parameters[@ "q" ] = @ "base" ;
  36.  
  37. // Send the request  
  38. [[AFHTTPRequestOperationManager manager] GET:@ "https://api.douban.com/v2/book/search" parameters:parameters success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
  39. NSLog(@ "%@" ,responseObject);
  40.  
  41. // Request successful call  
  42. // Pass the data out with a signal  
  43. [subscriber sendNext:responseObject];
  44.  
  45. [subscriber sendCompleted];
  46.  
  47.  
  48. } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
  49. // Request failed call  
  50.  
  51. }];
  52.  
  53. return nil;
  54. }];
  55.  
  56.  
  57. // When returning the data signal, map the dictionary in the data into a model signal and pass it out  
  58. return [requestSignal map:^id(NSDictionary *value) {
  59. NSMutableArray *dictArr = value[@ "books" ];
  60.  
  61. // Convert dictionary to model, traverse all elements in the dictionary, map them all into models, and generate an array  
  62. NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
  63.  
  64. return [Book bookWithDict:value];
  65. }]array];
  66.  
  67. return modelArr;
  68. }];
  69.  
  70. }];
  71.  
  72. // Get the requested data  
  73. [_reuqesCommand.executionSignals.switchToLatest subscribeNext:^(NSArray *x) {
  74.  
  75. // With new data, refresh the table  
  76. _models = x;
  77.  
  78. // Refresh the table  
  79. [self.tableView reloadData];
  80.  
  81. }];
  82. }
  83.  
  84. #pragma mark - UITableViewDataSource
  85.  
  86. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  87. {
  88. return self.models.count;
  89. }
  90.  
  91. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  92. {
  93. static NSString *ID = @ "cell" ;
  94. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
  95. if (cell == nil) {
  96.  
  97. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
  98. }
  99.  
  100. Book *book = self.models[indexPath.row];
  101. cell.detailTextLabel.text = book.subtitle;
  102. cell.textLabel.text = book.title;
  103.  
  104. return cell;
  105. }
  106.  
  107. @end  

<<:  Android slide close activity

>>:  Cool horizontal elastic menu

Recommend

Meizu PRO 6 Plus review: It can still be an Android flagship without Qualcomm

Before November 30, when people talked about this...

Promotion tips in the circle of friends in the education industry!

During the Spring Festival of 2020, the outbreak ...

50 articles on the operation and delivery of Xiaohongshu brand accounts

Although I am writing about how brands should ope...

[Children's Programming] "Yuanfudao Programming" L1+L2

[Children's Programming] Introduction to L1+L...

The traffic pattern of information flow video ads!

After working in advertising for so many years, w...

The embarrassment of Google’s smart hardware: bad luck, or a cycle of fate?

Do you still remember Google Glass ? This smart h...