目錄
一、ReactiveCocoa結(jié)合了多種編程風(fēng)格
二、在MVVM的項目中,為何總是會提到RAC呢?
三、ReactiveCocoa一些概念和用法
之前使用ReactiveCocoa+MVVM做過一次項目,當(dāng)時使用它的原因就是感覺一個是新,一個是非常的簡潔,看起來很酷。
當(dāng)時別人問我ReactiveCocoa是什么,我都會回答:函數(shù)響應(yīng)式編程。當(dāng)時也不懂什么意思,那么現(xiàn)在再來回顧一下ReactiveCocoa。
一、ReactiveCocoa結(jié)合了多種編程風(fēng)格:
函數(shù)式編程(Functional Programming)
函數(shù)式編程思想:是把操作盡量寫成一系列嵌套的函數(shù)或者方法調(diào)用。
響應(yīng)式編程(Reactive Programming)
響應(yīng)式編程思想:不需要考慮調(diào)用順序,只需要知道考慮結(jié)果,類似于蝴蝶效應(yīng),產(chǎn)生一個事件,會影響很多東西,這些事件像流一樣的傳播出去,然后影響結(jié)果。
那么我們簡單舉個例子:
RACSignal *signalA = RACObserve(self, a);
RACSignal *signalB = RACObserve(self, b);
RAC(self.label, text) = [RACSignal combineLatest:@[signalA,signalB] reduce:^(NSInteger countA, NSInteger countB){
return [NSString stringWithFormat:@"結(jié)果:%ld",countA+countB];
}];
那看看我們通常的做法
- (void)sumAction
{
self.label.text = [NSString stringWithFormat:@"結(jié)果:%ld",self.a+self.b];
}
看起來這個操作放在了一個方法中,挺簡單的,可是每次調(diào)用self.a = X;或者self.b = Y;時,想要顯示正確的值,那么必定要再調(diào)用一次[self sumAction];
當(dāng)我們執(zhí)行順序為self.a = X; [self sumAction]; self.b = Y; 這個時候沒有得到我們想要的X+Y的值,這不是響應(yīng)式的。
如果我們將[self sumAction];的調(diào)用放在set方法中呢?那么確實是響應(yīng)式的了,但用到了至少兩個方法。
在MVVM的項目中,為何總是會提到RAC呢?
MVVM從MVC演化而來,為的是減少C的邏輯和業(yè)務(wù)。如果沒有RAC,那么我們得使用NSNotification、delegate、KVO等將V變化產(chǎn)生的數(shù)據(jù)傳給VM,從而改變M的值。NSNotification可能跨度大點,尤其delegate和KVO會免不了將業(yè)務(wù)再次放到C中,如果不放到C中,那么這個VM就會與這個View綁定得比較緊,而ViewModel有可能并不是只服務(wù)于特定的一個View,這樣的話使用更加松散的綁定關(guān)系能夠降低ViewModel和View之間的耦合度。
因此View一旦產(chǎn)生數(shù)據(jù)了扔信號扔給ViewModel,使用ReactiveCocoa更能體現(xiàn)其精髓。
注:大部分MVVM架構(gòu)都會使用ReactiveCocoa,但是使用ReactiveCocoa的iOS應(yīng)用不一定就是基于MVVM架構(gòu)的。MVVM的關(guān)鍵是要有View Model。
三、ReactiveCocoa一些概念和用法:
1.map:
map是將一張表中的值映射新值到另一張表中
NSArray*mappedArray=[array rx_mapWithBlock:^id(ideach){
return @(pow([each integerValue], 2));
}];
注:這是建立了一個新的數(shù)組,而不是將原數(shù)組的值進(jìn)行改變
這所對應(yīng)的是
NSMutableArray*mutableArray=[NSMutableArrayarrayWithCapacity:array.count];
for(NSNumber*numberinarray){[mutableArrayaddObject:@(pow([number integerValue], 2))];
}
NSArray*mappedArray=[NSArrayarrayWithArray:mutableArray];
這就是更高級別的函數(shù)更占優(yōu)勢的地方
2.filter(過濾)
3.fold(結(jié)合)
//合數(shù)值
NSNumber*sum=[array rx_foldWithBlock:^id(id?memo,id?each){
return @([memo integerValue] + ?[eachintegerValue]);
}];
//拼字符串
[[array rx_mapWithBlock:^id(id?each){
return [each stringValue];
}] rx_foldInitialValue:@""block:^id(id?memo,id?each){
return [memo stringByAppendingString:each];
}];
4.Streams and Sequences
一系列的值抽象的被稱為流,你可以認(rèn)為流就像一個管道,其中值從一端進(jìn)入從另一端放出。除非在管道的尾端當(dāng)值出來時你能訪問,訪問過去的值甚至是當(dāng)前值都是不可能的。沒關(guān)系,我們拭目以待。
一系列的值,是嗎?有點像一個列表,或在我們的例子中,一個數(shù)組。事實上,我們可以使用rac_sequence方法很容易地將一個NSArray轉(zhuǎn)化成流。
NSArray*array=@[@(1),@(2),@(3)];
RACSequence*stream=[array rac_sequence];//將數(shù)組轉(zhuǎn)化成流
[stream map:^id(id value){
return @(pow([value integerValue],2));
}];
NSLog(@"%@",[stream array]);//將流又轉(zhuǎn)化成數(shù)組
//原來項目中使用了這個方法是這樣的一個過程,map可以將字典轉(zhuǎn)化成對象,然后就成為了對象的數(shù)組
NSLog(@"%@",[[[array rac_sequence] map:^id(idvalue){
return @(pow([value integerValue],2));
}] array]);
ReactiveCocoa包含left fold和right fold。left
fold是從開始往結(jié)尾穿過一個數(shù)組,right fold是反的
5.Signals
信號是另一種類型的流,對比sequences(序列),信號是推驅(qū)動,新值通過管道推壓并且不能拉出,在以后他們把將被遞送的數(shù)據(jù)抽取出來。
信號包括三種不同的類型值:Next、Error、Completion.
值得注意的是,Error和Completion只能通過信號送出一次,并且只有一個被送出。

6.Subscriptions
[self.textField.rac_textSignal subscribeNext:^(id?x){
NSLog(@"New value:%@", x);
}error:^(NSError*error){
NSLog(@"Error: %@",error);
}completed:^{
NSLog(@"Completed.");
}];
在textfield中輸入字符時,發(fā)現(xiàn)信號不發(fā)送error值和當(dāng)信號deallocated的時候發(fā)送completion值,因此我們可以
[self.textField.rac_textSignal subscribeNext:^(idx){
NSLog(@"New value: %@", x);
}];
當(dāng)subscribe(訂閱)一個信號,將自動創(chuàng)建一個subscription對象,這個對象將自動保留,同時也保留了這個信號的subscribing狀態(tài),你可以手動處理訂閱者,但這不是典型的操作,一般在使用重用視圖的時候處理信號
7.Deriving State
宏RAC()有兩個參數(shù):一個對象和該對象的鍵值路徑。然后,它執(zhí)行一個單向的綁定右邊的值到鍵值路徑。值必須是對象,這就是為什么我們把布爾值包裝成一個NSNumber。
RACSignal?*validEmailSignal=[self.textField.rac_textSignalmap:^id(NSString?*value){
return @([value rangeOfString:@"@"].location!= NSNotFound);
}];
RAC(self.button,enabled)=validEmailSignal;
RAC(self.textField,textColor)=[validEmailSignalmap:^id(id?value){
if ([value boolValue]) {? ?? ? ? ? return [UIColor greenColor];
} else {
return [UIColor redColor];
}
}];
8.Commands
當(dāng)你想要發(fā)送一個信號的值對用戶交互事件響應(yīng),Commands是有用的。command的信號能被訂閱來稍后接收返回的輸出信號。
self.button.rac_command=?[[RACCommand alloc]initWithEnabled:validEmailSignal
signalBlock:^RACSignal *(idinput){
NSLog(@"Button was pressed.");
return [RACSignal empty];
}];
這個按鈕將保持disabled直到信號返回complete值(也可以是空值([RACSignal empty]))
9.RACSubject
信號提供者,自己可以充當(dāng)信號,又能發(fā)送信號。
創(chuàng)建方法:
(1)創(chuàng)建RACSubject
(2)訂閱信號
(3)發(fā)送信號
工作流程:
(1)訂閱信號時,內(nèi)部保存了訂閱者,和訂閱者響應(yīng)block
(2)當(dāng)發(fā)送信號的,遍歷訂閱者,調(diào)用訂閱者的nextBlock
注:如果訂閱信號,必須在發(fā)送信號之前訂閱信號,不然收不到信號,這也有別于RACReplaySubject
-(void)racSubjectTest
{
RACSubject *subject= [RACSubjectsubject];
[subject subscribeNext:^(idx) {
NSLog(@"1
%@,type:%@",x,NSStringFromClass(object_getClass(x)));
}];
[subject subscribeNext:^(idx) {
NSLog(@"2
%@,type:%@",x,NSStringFromClass(object_getClass(x)));
}];
[subjectsendNext:@1];
[subject subscribeNext:^(idx) {
NSLog(@"3
%@,type:%@",x,NSStringFromClass(object_getClass(x)));
}];
}
10.Hot and Cold Signals
信號通常是懶惰的,意味著它們只有在有人已經(jīng)訂閱它們的時候,它們才會工作和發(fā)送信號。每增加一個訂閱,工作就會重新執(zhí)行。對于繁瑣的操作,這是可以接受的,并且實際上這是所希望的。在ReactiveCocoa術(shù)語中,這種類型的信號被稱為“冷信號”。
有時,我們想工作立即被執(zhí)行,這種類型的信號被稱為“熱信號”。熱信號非常罕見被使用到。
11.Multicasting
Multicasting是指某個信號訂閱被共享于大量訂閱者的術(shù)語。信號,一般是“冷信號”,它有時是不可取的,執(zhí)行工作每次它都是訂閱的“冷信號”,這常常是當(dāng)副作用或工作時訂閱是昂貴的時候執(zhí)行,否則只有在適當(dāng)?shù)臅r候才會執(zhí)行。網(wǎng)絡(luò)請求浮現(xiàn)在腦海中。
于是我們創(chuàng)建來源于信號的RACMulticastConnection。你可以在RACSignal中使用publish方法或multicast:方法。前一種方法為您創(chuàng)建一個multicast的連接。后一種方法也做了同樣的事,但還采用RACSubject參數(shù)。這個subject是手動從底層信號發(fā)送值,每當(dāng)它被調(diào)用。然后,任何由底層信號發(fā)送的值有興趣訂閱連接的信號,相反(如果你提供一個subject,信號正好是這個subject)。

由于在默認(rèn)情況下信號是“冷信號”,每添加一個訂閱者,則它的工作被執(zhí)行。如果是這樣的話,那是不可取的,我們使用multicast的連接。

multicast的連接訂閱的信號,當(dāng)它已經(jīng)通過了新的值,發(fā)送這些值到信號(這作為一個公共屬性公開)。你可以多次訂閱這個信號,并且在訂閱時執(zhí)行的工作,只是一次