Use ReactiveCocoa to implement responsive programming for iOS platform ReactiveCocoa and responsive programming
Before talking about ReactiveCocoa, we should first introduce FRP (Functional Reactive Programming, responsive Programming), which has an example in Wikipedia:
In the imperative programming environment, a = B + c indicates that the expression result is assigned to a, and changing the value of B or c does not affect. However, in responsive programming, the value of a is updated with the update of B or c.
Excel is an example of responsive programming. A cell can contain a nominal value or a formula similar to "= B1 + C1". The value of a cell containing the formula varies according to the value of another cell.
ReactiveCocoa, short for RAC, is an Objective-C practice based on responsive programming ideas. It is an open-source Github project and you can find it here.
For more information about FRP and ReactiveCocoa, see this blog by leezhong.
ReactiveCocoa framework Overview
Let's take a look at the analogy mentioned in leezhong and the blog post to give you a good understanding of ReactiveCocoa:
We can imagine the signal as a faucet, but it is not water, but a glass ball (value). The diameter is the same as the diameter of the pipe. This ensures that the glass balls are arranged in sequence, no side-by-side (data is processed linearly and no concurrency occurs ). The switch of the faucet is disabled by default. It is enabled only when the receiver (subscriber) is available. In this way, as long as a new glass ball comes in, it will be automatically transmitted to the receiver. You can add a filter on the faucet. If it does not match the filter, you can also add a device to change the ball to meet your needs (map ). You can also combine multiple faucets into a new faucet (combineLatest: reduce :), so that as long as one of the faucets has a glass ball, the merged faucet will get the ball.
Next I will introduce each component of the ReactiveCocoa framework one by one.
Streams
Streams is represented by RACStream. It can be seen as a series of glass balls flowing in the water pipe. They pass through sequentially. Before the first glass ball arrives, you cannot get the second glass ball.
RACStream describes this form of linear flowing glass sphere, which is abstract and does not have a great significance. It is generally replaced by higher-level forms such as signals or sequences.
Signals
Signals is represented by the RACSignal class, that is, the faucet mentioned above. The core concept of ReactiveCocoa is Signal, which generally indicates the value to arrive in the future. Imagine glass balls coming out of the faucet one by one, only the receiver (subscriber) can obtain these glass balls (values ).
Signal sends the following three events to its receiver (subscriber). It is imagined that the faucet has an indicator to report its working status.-subscribeNext:error:completed:
Respond to different events
- Next the new glass ball (value) flowing from the tap)
- An error occurs when an error occurs when obtaining a new glass ball. Generally, an NSError object is sent to indicate where the error occurred.
- All the completed glass balls have arrived successfully. No more glass balls have been added.
A Signal of a life cycle can send any number of "next" events and an "error" or "completed" event (of course, "error" and "completed" can only appear)
SubjectsSubjects is represented by the RACSubject class and can be considered as a "mutable" signal/custom signal. It is a bridge to graft non-RAC code to the Signals world and is very useful. Well... This is very abstract. For example:
123 |
RACSubject * letters = [RACSubject subject]; RACSignal * signal = [letters sendNext: @ "a"]; |
We can see that@"a"
It is just an NSString object. to flow smoothly in a pipe, we need to use the force of RACSubject.
CommandsCommand is represented as a RACCommand class. For example, a simple registration interface:
123456789101112131415161718192021 |
RACSignal * formValid = [RACSignal combineLatest: @ [self. userNameField. rac_textSignal, self. emailField. rac_textSignal,] reduce: ^ (NSString * userName, NSString * email) {return @ (userName. length & gt; 0 & amp; email. length & gt; 0) ;}]; RACCommand * createAccountCommand = [RACCommandcommandWithCanExecuteSignal: formValid]; RACSignal * networkResults = [[[createAccountCommand addSignalBlock: ^ RACSignal * (idvalue) {//... network Interaction code}] switchToLatest] deliverOn: [RACSchedulermainThreadScheduler]; // bind the UI state of the create button and click the event [[self. createButtonrac_signalForControlEvents: UIControlEventTouchUpInside] executeCommand: createAccountCommand]; |
SequencesSequence is represented by the RACSequence class, which can be simply regarded as the NSArray In the RAC world. RAC has increased-rac_sequence
The collection classes such as NSArray can be directly converted to RACSequence.
SchedulersScheders is represented by the RACScheduler class, similar to GCD, but schedulers support cancellationbut schedulers support cancellation, and always execute serially.
Simple use of ReactiveCocoaLet's take a look at the usage of RAC through some simple examples.
SubtasksReceive-subscribeNext:
-subscribeError:
-subscribeCompleted:
1234567 |
RACSignal * letters = [@ "a B c d e f g h I" componentsSeparatedByString: @ ""]. rac_sequence.signal; // output a B C D... [Letters subscribeNext: ^ (NSString * x) {NSLog (@ "% @", x) ;}]; |
Injecting effectsInjection Effect-doNext:
-doError:
-doCompleted:
, You should understand the following annotations:
1234567891011121314151617181920 |
_ Blockunsignedsubscriptions = 0; RACSignal * loggingSignal = [RACSignalcreateSignal: ^ RACDisposable * (id & lt; RACSubscriber & gt; subscriber) {subscriptions ++; [subscribersendCompleted]; returnnil;}]; // loggingSignal = [loggingSignaldoCompleted: ^ {NSLog (@ "about to complete subscri% u", subscriptions);}]; // output: // about to complete subpartition 1 // subpartition 1 [loggingSignalsubscribeCompleted: ^ {NSLog (@ "subpartition % u", subscriptions);}]; |
Mapping-map:
Ing can be seen as the transformation and re-assembly of the glass sphere
1234567 |
RACSequence * letters = [@ "a B c d e f g h I" componentsSeparatedByString: @ ""]. rac_sequence; // Contains: aa bb cc dd ee ff gg hh IIRACSequence * mapped = [letters map: ^ (NSString * value) {return [value stringByAppendingString: value];}]; |
Filtering-filter:
Filter. glass balls that do not meet the requirements cannot pass
1234567 |
RACSequence * numbers = [@ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString: @ ""]. rac_sequence; // Contains: 2 4 6 8 RACSequence * filtered = [numbersfilter: ^ BOOL (NSString * value) {return (value. intValue % 2) = 0;}]; |
Concatenating-concat:
After splicing a pipe to another pipe
123456 |
RACSequence * letters = [@ "a B c d e f g h I" componentsSeparatedByString: @ ""]. rac_sequence; RACSequence * numbers = [@ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString: @ ""]. rac_sequence; // Contains: a B c d e f g h I 1 2 3 4 5 6 7 8 9 RACSequence * concatenated = [letters concat: numbers]; |
Flattening-flatten:
Sequences are concatenated
1234567 |
RACSequence * letters = [@ "a B c d e f g h I" componentsSeparatedByString: @ ""]. rac_sequence; RACSequence * numbers = [@ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString: @ ""]. rac_sequence; RACSequence * sequenceOfSequences = @ [letters, numbers]. rac_sequence; // Contains: a B c d e f g h I 1 2 3 4 5 6 7 8 9 RACSequence * flattened = [sequenceOfSequencesflatten]; |
Signals are merged)
12345678910111213141516171819202122 |
RACSubject * letters = [RACSubject subject]; RACSubject * numbers = [RACSubject subject]; RACSignal * random = [RACSignal createSignal: ^ RACDisposable * (id & lt; RACSubscriber & gt; subscriber) {[subscriber sendNext: letters]; [subscriber sendNext: numbers]; [subscriber sendCompleted]; return nil ;}]; RACSignal * flattened = [signalOfSignals flatten]; // Outputs: A 1 B C 2 [flattened subscribeNext: ^ (NSString * x) {NSLog (@ "% @", x) ;}]; [letters sendNext: @ "A"]; [numbers sendNext: @ "1"]; [letters sendNext: @ "B"]; [letters sendNext: @ "C"]; [numbers sendNext: @ "2"]; |
Mapping and flattening-flattenMap:
Map first and then flatten
123456789101112131415161718192021222324252627282930 |
RACSequence * numbers = [@ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString: @ ""]. rac_sequence; // Contains: 1 1 2 2 3 3 3 4 5 5 6 6 6 7 8 8 9 RACSequence * extended = [numbersflattenMap: ^ (NSString * num) {return @ [num, num]. rac_sequence;}]; // Contains: 1 _ 3 _ 5 _ 7 _ 9_RACSequence * edited = [numbersflattenMap: ^ (NSString * num) {if (num. intValue % 2 = 0) {return [RACSequenceempty];} else {NSString * newNum = [numstringByAppendingString: @ "_"]; return [RACSequencereturn: newNum];}]; RACSignal * letters = [@ "a B c d e f g h I" componentsSeparatedByString: @ ""]. rac_sequence.signal; [[letters flattenMap: ^ (NSString * letter) {return [character: letter] ;}] subscribeCompleted: ^ {NSLog (@ "All database entries saved successfully. ") ;}]; |
Sequencing-then:
12345678910111213 |
RACSignal * letters = [@ "a B c d e f g h I" componentsSeparatedByString: @ ""]. rac_sequence.signal; // the new TAP only contains: 1 2 3 4 5 6 7 8 9 /// but when receiving, the old tap doNext will still be executed, therefore, a B C D E F G H IRACSignal * sequenced = [[letters doNext: ^ (NSString * letter) {NSLog (@ "% @", letter);}] then: ^ {return [@ "1 2 3 4 5 6 7 8 9" componentsSeparatedByString: @ ""]. rac_sequence.signal;}]; |
Merging+merge:
Merge the faucets mentioned above in flatten
123456789101112131415 |
RACSubject * letters = [RACSubjectsubject]; RACSubject * numbers = [RACSubjectsubject]; RACSignal * merged = [RACSignalmerge: @ [letters, numbers]; // Outputs: A 1 B C 2 [mergedsubscribeNext: ^ (NSString * x) {NSLog (@ "% @", x) ;}]; [letterssendNext: @ "A"]; [numberssendNext: @ "1"]; [letterssendNext: @ "B"]; [letterssendNext: @ "C"]; [numberssendNext: @ "2"]; |
Combining latest values+combineLatest:
Retrieve the latest glass ball from each tap at any time
1234567891011121314151617181920 |
RACSubject * letters = [RACSubject subject]; RACSubject * numbers = [RACSubject subject]; RACSignal * combined = [RACSignal combineLatest: @ [letters, numbers] reduce: ^ (NSString * letter, NSString * number) {return [letter stringByAppendingString: number] ;}]; // Outputs: B1 B2 C2 C3 [combined subscribeNext: ^ (id x) {NSLog (@ "% @", x) ;}]; [letters sendNext: @ "A"]; [letters sendNext: @ "B"]; [numbers sendNext: @ "1"]; [numbers sendNext: @ "2"]; [letters sendNext: @ "C"]; [numbers sendNext: @ "3"]; |
Switching-switchToLatest:
Retrieve the latest glass ball from the specified faucet
1234567891011121314151617181920212223 |
RACSubject * letters = [RACSubjectsubject]; RACSubject * numbers = [RACSubjectsubject]; RACSubject * signalOfSignals = [RACSubjectsubject]; RACSignal * switched = [Outputs]; // Outputs: a B 1 D [switchedsubscribeNext: ^ (NSString * x) {NSLog (@ "% @", x) ;}]; [signalOfSignalssendNext: letters]; [letterssendNext: @ "A"]; [letterssendNext: @ "B"]; [signalOfSignalssendNext: numbers]; [letterssendNext: @ "C"]; [numberssendNext: @ "1"]; [signalOfSignalssendNext: letters]; [numberssendNext: @ "2"]; [letterssendNext: @ "D"]; |
Common macro RAC can be seen as the association between values of a certain attribute and some signals.
1234 |
RAC (self. submitButton. enabled) = [RACSignal combineLatest: @ [self. usernameField. rac_textSignal, self. passwordField. rac_textSignal] reduce: ^ id (NSString * userName, NSString * password) {return @ (userName. length & gt; = 6 & amp; password. length & gt; = 6) ;}]; |
RACObserve listens for attribute changes and uses block KVO
1234 |
[RACObserve (self. textField, text) subscribeNext: ^ (NSString * newName) {NSLog (@ "% @", newName);}]; |
UI EventRAC provides a lot of category for the system UI, which is very good, such as changes to the UITextView and UITextField text boxes.rac_textSignal
, UIButton Pressrac_command
And so on.
LastWith RAC, you don't have to worry about the time when the value will change. You just need to perform a simple step after the data comes.
After talking about this, let's look back at leezhong's metaphor and the final diagram of the Article. Let's sort it out. I am also a beginner. I am very excited to present this blog. You are welcome to discuss it. If you have any mistakes, please criticize and correct them.
ReferenceHttps://github.com/ReactiveCocoa/ReactiveCocoa
Https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.md
Https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md
Http://vimeo.com/65637501
Http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
Http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.htmlhttp://nshipster.com/reactivecocoa/