ReactiveCocoa
ºÍ CocoaPods Ò»ÑùÒ²ÊÇĿǰºÜÁ÷ÐÐµÄ ÄÜ·½±ãºÍÌá¸ßЧÂʵĿò¼Ü.
Æäʵ²»¹âios ѧϰÈκÎÒ»ÃÅÓïÒô×î¿ì×îÖ±½ÓµÄ·½·¨¾ÍÊÇÉÏÊÖ. Èç¹ûµ¥µ¥ÊÇ¿´¿´ÎĵµÖ»ÄÜÊÇ×ßÂí¹Û»¨µÃµ½Ò»¸ö¸ÐÐÔÈÏʶ°ÕÁË.ǰÁ½Ìì֮ǰһ¸ö¹«Ë¾µÄ²âÊÔÈËÔ±¸úÎÒÁªÏµËµÈçºÎѧϰJAVA,ÎÒÎÊËûÊÇÔõôѧµÄ,Ëû˵´ÓÍøÉÏÏÂÁ˺öàÊÓÆµ½Ì³Ì,Ò»Ö±ÔÚ¿´ÊÓÆµ½Ì³Ì,Ò²ÂòÁËһЩÊé.
ÎÒÎÊËûÇÃÁ˶àÉÙ´úÂë,Ëû˵ºÜÉÙ. Æäʵ¸úËûÒ»ÑùµÄÈ˲»ÉÙ. ÎÒ¾õµÃÈκÎÊÂÇé¶¼²»¼òµ¥,ÒòΪ¿´ÆðÀ´¼òµ¥µÄÊÂÇé×Ô¼ºÃ»ÓÐʵ¼Ê×ö¹ýµÄ»°
ÍùÍù»áÓöµ½¸÷ÖÖ¸÷ÑùµÄÎÊÌâ. ÕâÒ²ÊÇΪʲôÔÚ¹¤×÷ÖÐÖÆ¶¨ÏîÄ¿½ø¶È¼Æ»®µÄʱºò ÎÒ¾¡Á¿¸ø×Ô¼ººÍÍŶÓÖеÄÈ˶àÕùȡʱ¼äµÄ×îÖ÷ÒªµÄÒ»¸öÒòËØ.
ºÃÁË,×Ô¼ºÒ»Ð©†ªàº͸ÐÎò. ÏÂÃæ¿ªÊ¼Õýʽ.
What is Reactive Cocoa?
RAV is an Object-C framework for Functional
Reavtive Programming;
Á½¸ö¹Ø¼üµã:
1:framework ¼ÈÈ»ÊÇframework ÄǾ͸úÄãÓÃµÄÆäËûframeworkÃ»Ê²Ã´Çø±ð
Î޷ǾÍÊǼӵ½¹¤³ÌÖÐÒýÓðÕÁË.²»×ö¶àÓà½âÊÍ
2:Functional Reavtive Programming ʵʱÏìӦʽ±à³Ì
ϺÃ×ÒýÓñ»°¢ÀïÊÕ¹ººóÏÖÔÚÏÈÉϵÄmac°æ±¾µÄϺÃ×ÒôÀÖ¾ÍÊÇÓÃReactive
Cocoa¿ª·¢µÄ.Óð¢ÀïÈË×Ô¼ºµÄ»°À´Ëµ¾ÍÊÇ
ºÃ¶«Î÷°¡£¬ÒÔǰÎÒÃÇÓà KVO »ò Notification À´×Ô¶¯°ó¶¨Êý¾Ý£¬¸ÄÓÃ
ReactiveCocoa дÒԺ󣬴úÂë½á¹¹¸ü¼Ñ¼òµ¥ÇåÎú£¬Í¬Ê±´úÂëÐÐÊýÖ±½Ó¼õÉÙ 60% ÒÔÉÏ
À´µãÖ±¹ÛµÄ¶Ô±È°É,±ÈÈçÎÒÃÇÏëҪʵÏÖÒ»¸öÐèÇó:µ±±äÁ¿ÖеÄ×Ö·û´®¸Ä±äºó¼´Ê±×ö³öÏàÓ¦µÄ·´À¡
ÎÒÃÇÓÃKVO ÐèÒªÈçÏÂÕâÑù×ö
ÉÏÃæÒ»Ûç´úÂë ÔÚÓ¦ÓÃReactive Cocoaºó Ö»Óж̶ÌÒ»ÐÐ
// In your viewDidLoad/awakeFromNib/init [self addObserver:self forKeyPath:@"someString" options:NSKeyValueObservingOptionNew context:&someStringChangeContext];
// In dealloc
[self removeObserver:self
forKeyPath:@"someString"
context:&someStringChangeContext];
// Elsewhere in your class
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == &someStringChangeContext) {
if ([keyPath isEqualToString:@"someString"])
{
// Do a bunch of stuff here
}
}
} |
ÉÏÃæÒ»Ûç´úÂë ÔÚÓ¦ÓÃReactive Cocoaºó Ö»Óж̶ÌÒ»ÐÐ
[RACObserve(self, someString) distinctUntilChanged] subscribeNext:^(NSString *string) { // Do a bunch of things here, just like you would with KVO }]; |
¿ªÊ¼¶¯ÊÖ
ÒòΪÎÒ֮ǰ°²×°ÁË CocoaPods ,ËùÒÔÎÒÕâ´ÎдµÄReactiveCocoaDemo
ÊÇ»ùÓÚCocoaPodsµÄ.Èç¹ûûÓа²×°µÄ¿ÉÒÔͯЬ,¿ÉÒÔÖ±½Ó´ÓgithubÉÏÏÂÔØ ReactiveCocoa
ǰÆÚ¹¤×÷:
1.´ò¿ªxcode ´´½¨Ò»¸ö¹¤³Ì,ÎÒÃüÃûµÄ¹¤³ÌÃûΪReactiveCocoaDemo,
2.Öն˵½¹¤³Ì·¾¶ÏÂ
cd ReactiveCocoaDemo/ pod search ReactiveCocoa |
3.ÅäÖÃÒÀÀµÎļþ
vi Podfile platform :ios,'5.0' pod 'ReactiveCocoa' wq |
4.ÏÂÔØÎļþ
5:´ò¿ª¡°Build Phases¡± Ñ¡ÖÐÏàÓ¦µÄtarget, Ìí¼Ó RAC
µ½ ¡°Link Binary With Libraries¡±.¼ÓÉÏlibReactiveCocoa-iOS.a
6:ÔÚHeader Search Paths ÖÐÌí¼Ó$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include"
7:ÔÚ ¡°Other Linker Flags¡± Ìí¼Ó -ObjC
8:ÔÚReactiveCocoaDemo-Prefix.pch ½«RACµÄÍ·Îļþ¼Ó½øÈ¥
#import "ReactiveCocoa.h" |
ÏÖÔÚҪʵÏÖÈçϹ¦ÄÜ
Á½¸öÊäÈë¿ò Ò»¸öÌáʾµÄLable Ò»¸ö°´Å¥,
1.µ±Á½¸öÊäÈë¿òµÄÄÚÈÝÏàͬʱ ÌáʾÎÄ×ÖÏÔʾ¡°³É¹¦¡± °´Å¥¿ÉÒÔµã»÷
2.µ±ÈÎÒâÒ»¸öÊäÈë¿òûÓÐÊäÈëÄÚÈÝʱ ÌáʾÎÄ×ÖÏÔʾ¡°ÇëÊäÈ롱 °´Å¥²»¿Éµã»÷
3.µ±ÊäÈë¿ò·Ç¿ÕÇÒÁ½¸öÊäÈëÄÚÈݲ»Í¬Ê± Ìáʾ¡°ÇëÖØÐÂÊäÈ롱 °´Å¥²»¿Éµã»÷
ÏÂÃæÊDz¿·ÖʵÏÖ´úÂë:
@weakify(self); [[RACObserve(self, warningText) filter:^(NSString *newString) { self.resultLabel.text = newString; return YES; // return [newString hasPrefix:@"Success"]; }] subscribeNext:^(NSString *newString) { @strongify(self); self.bt.enabled = [newString hasPrefix:@"Success"]; }];
RAC(self,self.warningText) = [RACSignal combineLatest:@[
RACObserve(self,self.input.text),RACObserve(self,
self.verifyInput.text)]
reduce:^(NSString *password, NSString *passwordConfirm)
{
if ([passwordConfirm isEqualToString:password])
{
return @"Success";
}
else if([password length] == 0 || [passwordConfirm
length] ==0 )
{
return @"Please Input";
}
else
return @"Input Error";
}
];
|
¶ÔÓ¦¹ØÏµÈçÏÂͼËùʾ

ѧ¹ýC++ µÄÓ¦¸Ã¾õµÃÕâ¸öºÜÀàËÆÓÚQtÖеÄÐźŲۻúÖÆ
ÐèҪ˵Ã÷µÄÊÇ ÒòΪRACºÜ´ó³Ì¶ÈÉÏÊÇÒÀÀµÓÚBlockµÄ.ËùÒÔÔÚRACÇ°ÃæÎÒÃǼÓÉÏ@weakify(my_variable)
±ÜÃâÑ»·ÒýÓÃ,È»ºóÔÚÿһ¸öRAC¿éÖÐΪÁË·ÀÖ¹ÌáǰÊÍ·ÅÎÒÃÇÐèÒªÓÃ@strongify(my_variable)À´¶Ô¶ÔÏó½øÐгÖÓÐ.
ÏêÇéµã»÷
ÔËÐÐЧ¹ûÈçÏÂ

Îĵµ¸½Â¼
Subscription
The [-subscribe¡][RACSignal] methods
give you access to the current and future values in
a signal:
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// Outputs: A B C D
[letters subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}]; |
For a cold signal, side effects will be performed once
per subscription :
__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^
RACDisposable * (id<RACSubscriber> subscriber)
{
subscriptions++;
[subscriber sendCompleted];
return nil;
}];
// Outputs:
// subscription 1
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u", subscriptions);
}];
// Outputs:
// subscription 2
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u", subscriptions);
}]; |
This behavior can be changed using a [connection][Connections].
Injecting effects
The [-do¡][RACSignal+Operations] methods
add side effects to a signal without actually subscribing
to it:
__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^
RACDisposable * (id<RACSubscriber> subscriber)
{
subscriptions++;
[subscriber sendCompleted];
return nil;
}];
// Does not output anything yet
loggingSignal = [loggingSignal doCompleted:^{
NSLog(@"about to complete subscription %u",
subscriptions);
}];
// Outputs:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u", subscriptions);
}]; |
Transforming streams
These operators transform a single stream into a new
stream.
Mapping
The [-map:][RACStream] method is used
to transform the values in a stream, and create a new
stream with the results:
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
// Contains: AA BB CC DD EE FF GG HH II
RACSequence *mapped = [letters map:^(NSString
*value) {
return [value stringByAppendingString:value];
}]; |
Filtering
The [-filter:][RACStream] method uses
a block to test each value, including it into the resulting
stream only if the test passes:
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 2 4 6 8
RACSequence *filtered = [numbers filter:^ BOOL
(NSString *value) {
return (value.intValue % 2) == 0;
}]; |
Combining streams
These operators combine multiple streams into a single
new stream.
Concatenating
The [-concat:][RACStream] method appends one stream's
values to another:
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
The [-flatten][RACStream] operator is
applied to a stream-of-streams, and combines their values
into a single new stream.
Sequences are concatenated :
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 = [sequenceOfSequences
flatten]; |
Signals are merged :
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> 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
Flattening isn't that interesting on
its own, but understanding how it works is important
for [-flattenMap:][RACStream].
-flattenMap: is used to transform each
of a stream's values into a new stream . Then, all of
the streams returned will be flattened down into a single
stream. In other words, it's -map: followed by -flatten
.
This can be used to extend or edit sequences:
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8
9 9
RACSequence *extended = [numbers flattenMap:^(NSString
*num) {
return @[ num, num ].rac_sequence;
}];
// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence *edited = [numbers flattenMap:^(NSString
*num) {
if (num.intValue % 2 == 0) {
return [RACSequence empty];
} else {
NSString *newNum = [num stringByAppendingString:@"_"];
return [RACSequence return:newNum];
}
}]; |
Or create multiple signals of work which are automatically
recombined:
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
[[letters
flattenMap:^(NSString *letter) {
return [database saveEntriesForLetter:letter];
}]
subscribeCompleted:^{
NSLog(@"All database entries saved successfully.");
}]; |
Combining signals
These operators combine multiple signals into a single
new [RACSignal][].
Sequencing
[-then:][RACSignal+Operations] starts
the original signal, waits for it to complete, and then
only forwards the values from a new signal:
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// The new signal only contains: 1 2 3 4 5 6
7 8 9
//
// But when subscribed to, it also outputs: A
B C D E F G H I
RACSignal *sequenced = [[letters
doNext:^(NSString *letter) {
NSLog(@"%@", letter);
}]
then:^{
return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@"
"].rac_sequence.signal;
}]; |
This is most useful for executing all the side effects
of one signal, then
starting another, and only returning the second signal's
values.
Merging
The [+merge:][RACSignal+Operations]
method will forward the values from many signals into
a single stream, as soon as those values arrive:
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
// Outputs: A 1 B C 2
[merged subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"]; |
Combining latest values
The [+combineLatest:][RACSignal+Operations]
and +combineLatest:reduce: methods will watch multiple
signals for changes, and then send the latest values
from all of them when a change occurs:
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"]; |
Note that the combined signal will only
send its first value when all of the inputs have sent
at least one. In the example above, @"A" was
never forwarded because numbers had not sent a value
yet.
Switching
The [-switchToLatest][RACSignal+Operations]
operator is applied to a signal-of-signals, and always
forwards the values from the latest signal:
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSubject *signalOfSignals = [RACSubject subject];
RACSignal *switched = [signalOfSignals switchToLatest];
// Outputs: A B 1 D
[switched subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[signalOfSignals sendNext:numbers];
[letters sendNext:@"C"];
[numbers sendNext:@"1"];
[signalOfSignals sendNext:letters];
[numbers sendNext:@"2"];
[letters sendNext:@"D"]; |
|