ReactiveCocoa框架概覽
網(wǎng)上有個(gè)說法對(duì)有個(gè)ReactiveCocoa很好的理解:
可以把信號(hào)想象成水龍頭穴豫,只不過里面不是水怀大,而是玻璃球(value)纱兑,直徑跟水管的內(nèi)徑一樣,這樣就能保證玻璃球是依次排列化借,不會(huì)出現(xiàn)并排的情況(數(shù)據(jù)都是線性處理的潜慎,不會(huì)出現(xiàn)并發(fā)情況)。水龍頭的開關(guān)默認(rèn)是關(guān)的蓖康,除非有了接收方(subscriber)铐炫,才會(huì)打開。這樣只要有新的玻璃球進(jìn)來蒜焊,就會(huì)自動(dòng)傳送給接收方倒信。可以在水龍頭上加一個(gè)過濾嘴(filter)泳梆,不符合的不讓通過堤结,也可以加一個(gè)改動(dòng)裝置,把球改變成符合自己的需求(map)鸭丛。也可以把多個(gè)水龍頭合并成一個(gè)新的水龍頭(combineLatest:reduce:)竞穷,這樣只要其中的一個(gè)水龍頭有玻璃球出來,這個(gè)新合并的水龍頭就會(huì)得到這個(gè)球鳞溉。
下面我來逐一介紹ReactiveCocoa框架的每個(gè)組件
Streams
Streams 表現(xiàn)為RACStream類瘾带,可以看做是水管里面流動(dòng)的一系列玻璃球,它們有順序的依次通過熟菲,在第一個(gè)玻璃球沒有到達(dá)之前看政,你沒法獲得第二個(gè)玻璃球朴恳。
RACStream描述的就是這種線性流動(dòng)玻璃球的形態(tài),比較抽象允蚣,它本身的使用意義并不很大于颖,一般會(huì)以signals或者sequences等這些更高層次的表現(xiàn)形態(tài)代替。
Signals
Signals 表現(xiàn)為RACSignal類嚷兔,就是前面提到水龍頭森渐,ReactiveCocoa的核心概念就是Signal,它一般表示未來要到達(dá)的值冒晰,想象玻璃球一個(gè)個(gè)從水龍頭里出來同衣,只有了接收方(subscriber)才能獲取到這些玻璃球(value)。
Signal會(huì)發(fā)送下面三種事件給它的接受方(subscriber)壶运,想象成水龍頭有個(gè)指示燈來匯報(bào)它的工作狀態(tài)耐齐,接受方通過-subscribeNext:error:completed:
對(duì)不同事件作出相應(yīng)反應(yīng)
- next 從水龍頭里流出的新玻璃球(value)
- error 獲取新的玻璃球發(fā)生了錯(cuò)誤,一般要發(fā)送一個(gè)NSError對(duì)象蒋情,表明哪里錯(cuò)了
- completed 全部玻璃球已經(jīng)順利抵達(dá)埠况,沒有更多的玻璃球加入了
一個(gè)生命周期的Signal可以發(fā)送任意多個(gè)“next”事件,和一個(gè)“error”或者“completed”事件(當(dāng)然“error”和“completed”只可能出現(xiàn)一種)
Subjects
subjects 表現(xiàn)為RACSubject類棵癣,可以認(rèn)為是“可變的(mutable)”信號(hào)/自定義信號(hào)辕翰,它是嫁接非RAC代碼到Signals世界的橋梁,很有用浙巫。嗯金蜀。。的畴。 這樣講還是很抽象渊抄,舉個(gè)例子吧:
RACSubject *letters = [RACSubject subject];
RACSignal *signal = [letters sendNext:@"a"];
可以看到@"a"只是一個(gè)NSString對(duì)象,要想在水管里順利流動(dòng)丧裁,就要借RACSubject的力护桦。
Commands
與RACSignal 等元素不同,RACCommand 并不表示數(shù)據(jù)流煎娇,它只是一個(gè)繼承自 NSObject 的類二庵,但是它卻可以用來創(chuàng)建和訂閱用于響應(yīng)某些事件的信號(hào)。
@interface RACCommand<__contravariant InputType, __covariant ValueType> : NSObject
@end
它本身并不是一個(gè) RACStream 或者 RACSignal 的子類缓呛,而是一個(gè)用于管理 RACSignal 的創(chuàng)建與訂閱的類催享。
在 ReactiveCocoa 中的 FrameworkOverview 部分對(duì) RACCommand 有這樣的解釋:
A command, represented by the RACCommand class, creates and subscribes to a signal in response to some action. This makes it easy to perform side-effecting work as the user interacts with the app.
在用于與 UIKit 組件進(jìn)行交互或者執(zhí)行包含副作用的操作時(shí),RACCommand 能夠幫助我們更快的處理并且響應(yīng)任務(wù)哟绊,減少編碼以及工程的復(fù)雜度因妙。
直接舉個(gè)例子吧,比如一個(gè)簡(jiǎn)單的注冊(cè)界面:
RACSignal *formValid=[RACSignal
combineLatest:@[
self.userNameField.rac_textSignal,
self.emailField.rac_textSignal,
]
reduce:^(NSString *userName,NSString *email){
return@(userName.length>0
&&email.length>0);
}];
RACCommand *createAccountCommand = [RACCommand commandWithCanExecuteSignal:formValid];
RACSignal *networkResults=[[[createAccountCommand
addSignalBlock:^RACSignal *(idvalue){
//... 網(wǎng)絡(luò)交互代碼
}] switchToLatest] deliverOn:[RACSchedulermainThreadScheduler]];
// 綁定創(chuàng)建按鈕的 UI state 和點(diǎn)擊事件
[[self.createButtonrac_signalForControlEvents:UIControlEventTouchUpInside]executeCommand:createAccountCommand];
Sequences
sequence 表現(xiàn)為RACSequence類,可以簡(jiǎn)單看做是RAC世界的NSArray攀涵,RAC增加了-rac_sequence
方法铣耘,可以使諸如NSArray這些集合類(collection classes)直接轉(zhuǎn)換為RACSequence來使用。
Schedulers
scheduler 表現(xiàn)為RACScheduler類以故,類似于GCD;
but schedulers support cancellationbut schedulers support cancellation, and always execute serially.
ReactiveCocoa的簡(jiǎn)單使用
實(shí)踐出真知蜗细,下面就舉一些簡(jiǎn)單的例子,一起看看RAC的使用
Subscription
接收 -subscribeNext:
-subscribeError:
-subscribeCompleted:
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// 依次輸出 A B C D…
[letters subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
Injecting effects
注入效果 -doNext:
-doError:
-doCompleted:
怒详,看下面注釋應(yīng)該就明白了:
__block unsigned subscriptions=0;
RACSignal *loggingSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
subscriptions++;
[subscriber sendCompleted];
returnnil;
}];
// 不會(huì)輸出任何東西
loggingSignal=[loggingSignal doCompleted:^{
NSLog(@"about to complete subscription %u",subscriptions);
}];
// 輸出:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u",subscriptions);
}];
Mapping
-map:
映射炉媒,可以看做對(duì)玻璃球的變換、重新組裝
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
-filter:
過濾棘利,不符合要求的玻璃球不允許通過
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;
}];
Concatenating
-concat:
把一個(gè)水管拼接到另一個(gè)水管之后
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
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 (merge可以理解成把幾個(gè)水管的龍頭合并成一個(gè)橱野,哪個(gè)水管中的玻璃球哪個(gè)先到先吐哪個(gè)玻璃球)
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
-flattenMap:
先 map 再 flatten
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[RACSequenceempty];
}else{
NSString *newNum=[num stringByAppendingString:@"_"];
return[RACSequencereturn:newNum];
}
}];
RACSignal *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence.signal;
[[letters flattenMap:^(NSString *letter){
return[databasesaveEntriesForLetter:letter];
}] subscribeCompleted:^{
NSLog(@"All database entries saved successfully.");
}];
Sequencing
-then:
會(huì)等待completed事件的發(fā)送朽缴,然后再訂閱由then block返回的signal善玫。這樣就高效地把控制權(quán)從一個(gè)signal傳遞給下一個(gè)。
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// 新水龍頭只包含: 1 2 3 4 5 6 7 8 9
//
// 但當(dāng)有接收時(shí)密强,仍會(huì)執(zhí)行舊水龍頭doNext的內(nèi)容茅郎,所以也會(huì)輸出 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;
}];
Merging
+merge:
前面在flatten中提到的水龍頭的合并
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
+combineLatest:
任何時(shí)刻取每個(gè)水龍頭吐出的最新的那個(gè)玻璃球
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:
取指定的那個(gè)水龍頭的吐出的最新玻璃球
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"];
常用宏
RAC 可以看作某個(gè)屬性的值與一些信號(hào)的聯(lián)動(dòng)
RAC(self.submitButton.enabled) = [RACSignal combineLatest:@[self.usernameField.rac_textSignal, self.passwordField.rac_textSignal]reduce:^id(NSString *userName, NSString *password) {
return @(userName.length >= 6 && password.length >= 6);
}];
RACObserve 監(jiān)聽屬性的改變,使用block的KVO
[RACObserve(self.textField,text)subscribeNext:^(NSString *newName){
NSLog(@"%@",newName);
}];
UI Event
RAC為系統(tǒng)UI提供了很多category或渤,非常棒系冗,比如UITextView、UITextField文本框的改動(dòng)rac_textSignal
薪鹦,UIButton的的按下rac_command
等等掌敬。
常用的模式
map + switchToLatest
switchToLatest:
的作用是自動(dòng)切換signal of signals到最后一個(gè),比如的command.executionSignals
就可以使用switchToLatest:
池磁。
map:
的作用很簡(jiǎn)單奔害,對(duì)sendNext
的value
做一下處理,返回一個(gè)新的值地熄。
如果把這兩個(gè)結(jié)合起來就有意思了华临,想象這么個(gè)場(chǎng)景,當(dāng)用戶在搜索框輸入文字時(shí)端考,需要通過網(wǎng)絡(luò)請(qǐng)求返回相應(yīng)的hints雅潭,每當(dāng)文字有變動(dòng)時(shí),需要取消上一次的請(qǐng)求却特,就可以使用這個(gè)配搭扶供。簡(jiǎn)單演示一下:
NSArray *pins = @[@172230988, @172230947, @172230899, @172230777, @172230707];
__block NSInteger index = 0;
RACSignal *signal = [[[[RACSignal interval:0.1 onScheduler:[RACScheduler scheduler]]
take:pins.count]
map:^id(id value) {
return [[[HBAPIManager sharedManager] fetchPinWithPinID:[pins[index++] intValue]] doNext:^(id x) {
NSLog(@"這里只會(huì)執(zhí)行一次");
}];
}]
switchToLatest];
[signal subscribeNext:^(HBPin *pin) {
NSLog(@"pinID:%d", pin.pinID);
} completed:^{
NSLog(@"completed");
}];
// output
// 2014-06-05 17:40:49.851 這里只會(huì)執(zhí)行一次
// 2014-06-05 17:40:49.851 pinID:172230707
// 2014-06-05 17:40:49.851 completed
takeUntil
takeUntil:someSignal
的作用是當(dāng)someSignal sendNext
時(shí),當(dāng)前的signal就sendCompleted
裂明,someSignal就像一個(gè)拳擊裁判椿浓,哨聲響起就意味著比賽終止。
它的常用場(chǎng)景之一是處理cell的button的點(diǎn)擊事件,比如點(diǎn)擊Cell的詳情按鈕轰绵,需要push一個(gè)VC粉寞,就可以這樣:
[[[cell.detailButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(id x) {
// generate and push ViewController
}];
如果不加takeUntil:cell.rac_prepareForReuseSignal
,那么每次Cell被重用時(shí)左腔,該button都會(huì)被addTarget:selector
唧垦。
替換Delegate
出現(xiàn)這種需求,通常是因?yàn)樾枰獙?duì)Delegate的多個(gè)方法做統(tǒng)一的處理液样,這時(shí)就可以造一個(gè)signal出來振亮,每次該Delegate的某些方法被觸發(fā)時(shí),該signal就會(huì)sendNext鞭莽。
@implementation UISearchDisplayController (RAC)
- (RACSignal *)rac_isActiveSignal {
self.delegate = self;
RACSignal *signal = objc_getAssociatedObject(self, _cmd);
if (signal != nil) return signal;
/* Create two signals and merge them */
RACSignal *didBeginEditing = [[self rac_signalForSelector:@selector(searchDisplayControllerDidBeginSearch:)
fromProtocol:@protocol(UISearchDisplayDelegate)] mapReplace:@YES];
RACSignal *didEndEditing = [[self rac_signalForSelector:@selector(searchDisplayControllerDidEndSearch:)
fromProtocol:@protocol(UISearchDisplayDelegate)] mapReplace:@NO];
signal = [RACSignal merge:@[didBeginEditing, didEndEditing]];
objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return signal;
}
@end
rac_signalForSelector
rac_signalForSelector: 這個(gè)方法會(huì)返回一個(gè)signal坊秸,當(dāng)selector執(zhí)行完時(shí),會(huì)sendNext
澎怒,也就是當(dāng)某個(gè)方法調(diào)用完后再額外做一些事情褒搔。用在category會(huì)比較方便,因?yàn)镃ategory重寫父類的方法時(shí)喷面,不能再通過[super XXX]來調(diào)用父類的方法星瘾,當(dāng)然也可以手寫Swizzle來實(shí)現(xiàn),不過有了rac_signalForSelector:就方便多了惧辈。
rac_signalForSelector: fromProtocol:
可以直接實(shí)現(xiàn)對(duì)protocol的某個(gè)方法的實(shí)現(xiàn)(聽著有點(diǎn)別扭呢)琳状,比如,我們想實(shí)現(xiàn)UIScrollViewDelegate的某些方法盒齿,可以這么寫:
[[self rac_signalForSelector:@selector(scrollViewDidEndDecelerating:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(RACTuple *tuple) {
// do something
}];
[[self rac_signalForSelector:@selector(scrollViewDidScroll:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(RACTuple *tuple) {
// do something
}];
self.scrollView.delegate = nil;
self.scrollView.delegate = self;
注意念逞,這里的delegate需要先設(shè)置為nil,再設(shè)置為self边翁,而不能直接設(shè)置為self翎承,如果self已經(jīng)是該scrollView的Delegate的話。
有時(shí)倒彰,我們想對(duì)selector的返回值做一些處理审洞,但很遺憾RAC不支持,如果真的有需要的話待讳,可以使用Aspects
結(jié)語
這是搜集的一些RAC的常用方法芒澜,如果有不全的地方敬請(qǐng)諒解,但愿能帶來一些幫助创淡,有誤的地方也歡迎指正和探討痴晦。
分享一些關(guān)于RAC的文章,寫得都很好
『狀態(tài)』驅(qū)動(dòng)的世界:ReactiveCocoa
『可變』的熱信號(hào) RACSubject
Pull-Driven 的數(shù)據(jù)流 RACSequence
優(yōu)雅的 RACCommand
用于多播的 RACMulticastConnection
RAC 中的雙向數(shù)據(jù)綁定 RACChannel
理解 RACScheduler 的實(shí)現(xiàn)
從代理到 RACSignal
【長(zhǎng)篇高能】ReactiveCocoa 和 MVVM 入門
iOS MVVM+RAC 從框架到實(shí)戰(zhàn)
ReactiveCocoa自述:工作原理和應(yīng)用
美團(tuán)網(wǎng)官方博客之RACSignal的Subscription深入分析