第〇:寫在前面
1.什么是ReactiveCocoa?
Reactive
: 響應(yīng)式的、函數(shù)式的。
Cocoa
:蘋果開發(fā)框架名稱.
Reactive
+ Cocoa
= 具備函(Gao)數(shù)(Bi)式(Ge)編程思想的開發(fā)框架.
2.啥是編程思想?
常見的編程思想有:
- 面向過程:打開冰箱->把大象放冰箱里->關(guān)上冰箱門。
- 面向?qū)ο螅簩ο蟮纳砀唧w重三圍是屬性;對象洗衣做飯看孩子是行為。佛曰:一切皆可幻化為對象,Everything is Object。
- 鏈?zhǔn)骄幊蹋喝绻眠^Monsary肯定見過 ->
make.centerX.and.centerY.equalTo(self).with.offset(0.0);
其實質(zhì)是每個方法返回一個”返回值為自身且?guī)в幸粋€參數(shù)項“的Block,形成Block串聯(lián),也就是所謂的鏈?zhǔn)骄幊獭?/li> - 函數(shù)式編程:
思想上,函數(shù)式編程可謂博大精深,建議看這篇文章了解: 傳送門
形式上,函數(shù)是一等公民,常見函數(shù)(或閉包)作為參數(shù)或返回值傳遞、閉包嵌套、函數(shù)組合等形式。如:
[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop) {
NSLog(@"%@",number);
}];
- 響應(yīng)式編程:對于輸入的變動加以監(jiān)聽,響應(yīng)式地,每次產(chǎn)生對應(yīng)的輸出。
ReactiveCocoa具備函數(shù)式編程和響應(yīng)式編程思想,因此常被稱之為函數(shù)響應(yīng)式編程(Functional Reactive Programming)框架.
3.為啥用ReactiveCocoa?
因為它統(tǒng)一并簡化了蘋果原生框架大多場景,使得開發(fā)更為一致而且高效。
ReactiveCocoa統(tǒng)一了iOS開發(fā)中常用的Target、Delegate、KVO、通知、NSTimer、網(wǎng)絡(luò)異步回調(diào)等操作,并將相應(yīng)的事件處理代碼聚合在一處。
4.CocoaPod引入
由于ReactiveCocoa 3.0.0 版本及以下都是用純Objective-C語言,這篇文章基于純OC開發(fā),因此Podfile文件長這樣(建工程名字我這取的是RACDemo):
use_frameworks!
target 'RACDemo' do
project 'RACDemo'
pod 'ReactiveCocoa', '~> 3.0.0’
end
第一:基礎(chǔ)概念
ReactiveCocoa的核心是攜帶信息的信號進(jìn)行傳遞和訂閱的過程。
簡要示意圖如下:
接下來將會介紹與此相關(guān)的四個大的概念:
- 信號源
- 訂閱者
- 回收站(RACDisposable)
- 調(diào)度器(上圖中未提及)
1.信號源
- 信號的老祖宗 RACStream
RACStream,顧名思義,就是指信號流。其繼承結(jié)構(gòu)如下圖:
通常情況下不直接使用RACStream,而是用其子類進(jìn)行信號傳遞。
- 核心類RACSignal
最常用的信號類,主要功能是創(chuàng)建信號、配置訂閱者來訂閱信號。
信號在傳遞過程中包括正常傳遞、傳遞完成和傳遞失敗的狀態(tài),分別對應(yīng)下圖中next、completed和error。
- 序列信號RACSequence
指的是用來簡化OC中的集合操作的信號類
2.訂閱者
- 為了獲取某個信號源中的值,通常需要對該信號源進(jìn)行訂閱,這就產(chǎn)生了訂閱者的概念。
- 在ReactiveCocoa中所有實現(xiàn)了RACSubscriber協(xié)議的類都可以作為信號源的訂閱者。
ReactiveCocoa源碼中RACSubscriber協(xié)議規(guī)定了四個要實現(xiàn)的方法:
@protocol RACSubscriber <NSObject>
@required
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end
其中: -sendNext:
、-sendError:
和-sendCompleted
方法分別接收信號next、error和 completed事件;-didSubscribeWithDisposable:
方法用來接收代表某次訂閱的 RACDisposable(回收站)對象。
3.回收站(RACDisposable)
- 在ReactiveCocoa中并沒有專門的類來代表一次完整的訂閱行為,而間接地使用RACDisposable來表示訂閱行為。
- 訂閱完成或失敗時自動觸發(fā)回收機(jī)制,因為它主要做一些資源回收和垃圾清理的工作。
- 可以通過RACDisposable的
dispose
方法主動取消訂閱行為,不再接受信號傳遞的信息。
4.調(diào)度器(RACScheduler)
RACScheduler是對GCD的簡單封裝,用來調(diào)度訂閱任務(wù)。
第二:核心信號之RACSignal
創(chuàng)建、訂閱、發(fā)送、回收一個信號
- (void)testCreateSignal {
// 1.創(chuàng)建信號
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 每當(dāng)有訂閱者訂閱信號,就會調(diào)用此block。
// 3.發(fā)送信號
[subscriber sendNext:@"糖水"];
[subscriber sendNext:@"鹽水"];
[subscriber sendNext:@"辣椒水"];
// 如果不再發(fā)送數(shù)據(jù),最好發(fā)送信號完成,內(nèi)部會自動調(diào)用[RACDisposable disposable]取消訂閱信號。
// 5.訂閱完成
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// 7.當(dāng)信號發(fā)送完成或者發(fā)送錯誤,就會自動執(zhí)行這個block,取消訂閱信號。
// 執(zhí)行完Block后,當(dāng)前信號就不在被訂閱了。
NSLog(@"信號被銷毀");
}];
}];
// 2.訂閱信號,依次獲取步驟3中傳遞的信息
// (此方法隱藏了兩件事:一是創(chuàng)建訂閱者RACSubscriber,二是為信號配置訂閱者并觸發(fā)訂閱任務(wù)。其目的是簡化信號訂閱邏輯)
[siganl subscribeNext:^(id x) {
// 4.每當(dāng)有信號sendNext: ,就會調(diào)用此block.
NSLog(@"接收到數(shù)據(jù):%@",x);
} error:^(NSError *error) {
NSLog(@"信號發(fā)送失敗");
} completed:^{
// 6.第5步中[subscriber sendCompleted];執(zhí)行后直接走此處方法
NSLog(@"信號發(fā)送完成");
}];
// 8.繼續(xù)訂閱信號,依次獲取步驟3中傳遞的信息
[siganl subscribeNext:^(id x) {
// 每當(dāng)有信號sendNext: ,就會調(diào)用此block.
NSLog(@"接收到數(shù)據(jù):%@",x);
}];
}
// 最終控制臺輸出結(jié)果為:
2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一個訂閱者接收到:糖水
2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一個訂閱者接收到:鹽水
2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一個訂閱者接收到:辣椒水
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信號發(fā)送完成
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信號被銷毀
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二個訂閱者接收到:糖水
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二個訂閱者接收到:鹽水
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二個訂閱者接收到:辣椒水
2017-06-23 16:15:49.029 RACDemo[48262:2367413] 信號被銷毀
解讀
: 對于信號創(chuàng)建、訂閱、發(fā)送、回收,我們可以把整個ReactiveCocoa框架形象地類比為水管管道機(jī)制:
- 假裝你擁有一個空水管的整體管道(也就是
CocoaReactive
框架) - 信號(
RACSignal
)的創(chuàng)建猶如開啟一個入水閥閥門 - 信號攜帶的信息……舉個栗子:糖水、鹽水、辣椒水
- 創(chuàng)建信號(
createSignal:
),如同入水閥中陸續(xù)倒上這幾種水,擱著,等待流動(傳遞)(sendNext:
)。 - 訂閱者(RACSubscriber)相當(dāng)于一個出水閥,進(jìn)行訂閱(
subscribeNext:
)就是擰開出水閥,和被訂閱的入水閥(RACSignal
)連通,這樣水就開始流動,進(jìn)行傳遞(sendNext:
)。 - 一旦水流抵達(dá)出水閥后不再繼續(xù)管道流通(
sendCompleted:
),或者水在半路堵死了(sendError:
),這時候沒必要繼續(xù)保持出水閥和入水閥間的連通(或者說訂閱)狀態(tài)(節(jié)約用水,人人有責(zé)),會自動關(guān)閉出水閥閥門并阻斷連通,也就是觸發(fā)回收([RACDisposable disposableWithBlock:^{}
)行為解除訂閱,此時訂閱過程結(jié)束。 - 回收行為的代碼往往在信號創(chuàng)建時一并書寫;回收(
RACDisposable
)代表著一次訂閱過程的結(jié)束。 - 每個出水閥的訂閱(也就是每次擰開一個出水閥的閥門),都會立即接收到入水閥先后放進(jìn)去的信息(糖水、鹽水、辣椒水)。
- 如果有多個出水閥(訂閱者)訂閱一個入水閥的情況,則第一個出水閥被回收(擰上)后,再打開下一個出水閥訂閱,結(jié)束后再被回收,再擰開下一個出水閥……一次擰開一個、依開啟順序進(jìn)行。
畫個圖來表示上面的代碼吧:
第三:華麗的信號家族之RACSubject
- 上面的圖片描述了一般信號傳遞的機(jī)制,華麗版的信號傳遞無非是在此基礎(chǔ)上,修修水管管道,調(diào)整水閥間的連通關(guān)系等。
- 由于水閥水管實用性強(qiáng),生活家居必備,嵌入在現(xiàn)實生活可以連通城市地下網(wǎng)絡(luò),嵌入在蘋果Cocoa框架中,更是將原來不靈動的框架硬生生靈動起來,尤其是上文已經(jīng)提到的Target、Delegate、KVO、通知、NSTimer、網(wǎng)絡(luò)異步回調(diào)等,被信號化后結(jié)構(gòu)便一致起來。
下面說說更多有趣的信號場景
1. RACSubject
RACSubject和RACSignal的最大不同是,RACSubject既是信號的子類,同時實現(xiàn)了訂閱者RACSubscriber協(xié)議,因此也可以充當(dāng)訂閱者發(fā)送信息,于是乎,看個栗子:
- (void)testRACSubject {
// RACSubject使用步驟
// 1.創(chuàng)建信號 [RACSubject subject],跟RACSiganl不一樣,創(chuàng)建信號時沒有block。
// 2.訂閱信號 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 3.發(fā)送信號 sendNext:(id)value
// RACSubject:底層實現(xiàn)和RACSignal不一樣。
// 1.調(diào)用subscribeNext訂閱信號,只是把訂閱者保存起來,并且訂閱者的nextBlock已經(jīng)賦值了。
// 2.調(diào)用sendNext發(fā)送信號,遍歷剛剛保存的所有訂閱者,一個一個調(diào)用訂閱者的nextBlock。
// 1.創(chuàng)建信號
RACSubject *subject = [RACSubject subject];
// 2.訂閱信號
[subject subscribeNext:^(id x) {
// block調(diào)用時刻:當(dāng)信號發(fā)出新值,就會調(diào)用.
NSLog(@"第一個訂閱者%@",x);
}];
[subject subscribeNext:^(id x) {
// block調(diào)用時刻:當(dāng)信號發(fā)出新值,就會調(diào)用.
NSLog(@"第二個訂閱者%@",x);
}];
// 3.發(fā)送信號
[subject sendNext:@"糖水"];
[subject sendNext:@"鹽水"];
[subject sendNext:@"辣椒水"];
}
// 最終控制臺輸出結(jié)果為:
2017-06-23 16:12:13.565 RACDemo[48194:2363636] 第一個訂閱者接收到:糖水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二個訂閱者接收到:糖水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一個訂閱者接收到:鹽水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二個訂閱者接收到:鹽水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一個訂閱者接收到:辣椒水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二個訂閱者接收到:辣椒水
解讀
:
- 首先寫法上,
sendNext:
不用擠在信號創(chuàng)建的block中去寫了,因為RACSubject對象本身可以作為訂閱者調(diào)用此方法。 - 其次,RACSubject的訂閱方法(
subscribeNext:
)僅僅是擰開出水閥,此時進(jìn)水閥中還沒倒進(jìn)水。 - 當(dāng)主動倒水(
sendNext:
)時,會把本次放進(jìn)去的水傳遞給所有的出水閥 - RACSubject和RACSignal最大的不同在于放水(
sendNext:
)的時機(jī),前者是配置好出水口后主動流向所有出水口,后者是出水口打開后將入水閥門中的水引出。
畫個圖吧:
2. RACReplaySubject
不要被更長的名字嚇到,其實它更簡單。如果說RACSubject是對出水管都統(tǒng)一配置好再送水的過程,那么RACReplaySubject相當(dāng)于把水流都配置好等待被出水管引出的過程。簡單的代碼走起:
- (void)testRACReplaySubject {
// RACReplaySubject:底層實現(xiàn)和RACSubject不一樣。
// 1.調(diào)用sendNext發(fā)送信號,把值保存起來,然后遍歷剛剛保存的所有訂閱者,一個一個調(diào)用訂閱者的nextBlock。
// 2.調(diào)用subscribeNext訂閱信號,遍歷保存的所有值,一個一個調(diào)用訂閱者的nextBlock
// 如果想當(dāng)一個信號被訂閱,就重復(fù)播放之前所有值,需要先發(fā)送信號,在訂閱信號。
// 也就是先保存值,在訂閱值。
// 1.創(chuàng)建信號
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.發(fā)送信號
[replaySubject sendNext:@"糖水"];
[replaySubject sendNext:@"鹽水"];
[replaySubject sendNext:@"辣椒水"];
// 3.訂閱信號
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一個訂閱者接收到:%@",x);
}];
// 訂閱信號
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一個訂閱者接收到:%@",x);
}];
}
// 最終控制臺輸出結(jié)果為:
2017-06-26 17:52:39.670 RACDemo[61435:3788527] 第一個訂閱者接收到:糖水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一個訂閱者接收到:鹽水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一個訂閱者接收到:辣椒水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二個訂閱者接收到:糖水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二個訂閱者接收到:鹽水
2017-06-26 17:52:39.672 RACDemo[61435:3788527] 第二個訂閱者接收到:辣椒水
解讀
:
- 輸出的結(jié)果和RACSignal完全一致,只是寫法上略有不同
- 這意味著,繼承自RACSubject的RACReplaySubject實現(xiàn)了基本信號的功能
- 但是,RACReplaySubject還可以在上面的訂閱代碼后面,繼續(xù)
sendNext:
,實現(xiàn)和RACSubject一樣的功能.(有興趣的童鞋可以在上面代碼最后加一句[replaySubject sendNext:@"別的水"];
試一下,看看打印的結(jié)果)
對于上面的代碼,依舊畫個圖表示吧:
總結(jié)
: RACSubject實現(xiàn)每個水流群發(fā)到所有出水口,RACReplaySubject實現(xiàn)每個出水口接收所有水流。
擴(kuò)展
: 有人會把RACSignal和RACSubject中控制水流方式的不同,分為冷信號
和熱信號
這么一說。
- 冷,即為高冷,你不訂閱(打開出水口)就沒辦法收到信號(水流)信息,這說的就是RACSignal
- 熱,即為熱情,管你有木有訂閱,先送水再說,大不了沒人收到我的信號信息,這說的就是RACSubject。
- 冷信號能否變成熱信號,熱情起來呢?可以。接下來說說RACMulticastConnection~
3. RACMulticastConnection
常規(guī)RACSignal被訂閱多次時,每次都要將創(chuàng)建信號的Block中所有代碼(包括sendNext:
)方法執(zhí)行一遍,可否向RACSubject一樣,只sendNext:
一次就發(fā)送給所有訂閱者,也就是所謂的冷信號轉(zhuǎn)變?yōu)闊嵝盘?看代碼:
- (void)testRACMulticastConnection {
// RACMulticastConnection使用步驟:
// 1.創(chuàng)建信號 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 2.創(chuàng)建連接 RACMulticastConnection *connect = [signal publish];
// 3.訂閱信號,注意:訂閱的不在是之前的信號,而是連接的信號。 [connect.signal subscribeNext:nextBlock]
// 4.連接 [connect connect]
// RACMulticastConnection底層原理:
// 1.創(chuàng)建connect,connect.sourceSignal -> RACSignal(原始信號) connect.signal -> RACSubject
// 2.訂閱connect.signal,會調(diào)用RACSubject的subscribeNext,創(chuàng)建訂閱者,而且把訂閱者保存起來,不會執(zhí)行block。
// 3.[connect connect]內(nèi)部會訂閱RACSignal(原始信號),并且訂閱者是RACSubject
// 3.1.訂閱原始信號,就會調(diào)用原始信號中的didSubscribe
// 3.2 didSubscribe,拿到訂閱者調(diào)用sendNext,其實是調(diào)用RACSubject的sendNext
// 4.RACSubject的sendNext,會遍歷RACSubject所有訂閱者發(fā)送信號。
// 4.1 因為剛剛第二步,都是在訂閱RACSubject,因此會拿到第二步所有的訂閱者,調(diào)用他們的nextBlock
// 需求:假設(shè)在一個信號中發(fā)送請求,每次訂閱一次都會發(fā)送請求,這樣就會導(dǎo)致多次請求。
// 解決:使用RACMulticastConnection就能解決.
// RACMulticastConnection:解決重復(fù)請求問題
// 1.創(chuàng)建信號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"發(fā)送請求");
[subscriber sendNext:@"糖水"];
[subscriber sendNext:@"鹽水"];
[subscriber sendNext:@"辣椒水"];
return nil;
}];
// 2.創(chuàng)建連接
RACMulticastConnection *connect = [signal publish];
// 3.訂閱信號,
// 注意:訂閱信號,也不能激活信號,只是保存訂閱者到數(shù)組,必須通過連接,當(dāng)調(diào)用連接,就會一次性調(diào)用所有訂閱者的sendNext:
[connect.signal subscribeNext:^(id x) {
NSLog(@"第一個訂閱者接收到:%@",x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"第二個訂閱者接收到:%@",x);
}];
// 4.連接,激活信號
[connect connect];
}
// 最終控制臺輸出結(jié)果為:
2017-06-26 18:44:55.057 RACDemo[61893:3825826] 發(fā)送請求
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一個訂閱者接收到:糖水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二個訂閱者接收到:糖水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一個訂閱者接收到:鹽水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二個訂閱者接收到:鹽水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一個訂閱者接收到:辣椒水
2017-06-26 18:44:55.059 RACDemo[61893:3825826] 第二個訂閱者接收到:辣椒水
解讀
:
- RACMulticastConnection的廬山真面目依舊走RACSubject訂閱機(jī)制
- 恩,不畫示意圖了,同RACSubject~
第四:輕松點(diǎn),聊聊那些被封裝好的簡單信號及使用
如果,我說如果,理解ReactiveCocoa的原理很為難,那么使用它卻極其簡單……
- 監(jiān)聽是否實現(xiàn)了某方法 之
rac_signalForSelector
。
應(yīng)用場景:判斷某個代理是否實現(xiàn)某方法。
或者,下面栗子中監(jiān)控某Button所在的VC中是否實現(xiàn)了點(diǎn)擊方法
if ([self rac_signalForSelector:@selector(clickTestButton:)]) {
NSLog(@"點(diǎn)擊了測試按鈕");
}
- 替代KVO的
rac_valuesAndChangesForKeyPath
。
有了它,我已忘記原來的KVO長什么樣子
[[self rac_valuesAndChangesForKeyPath:@"testNum"
options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"new value info is %@", x);
}];
- 監(jiān)聽事件
rac_signalForControlEvents
,如點(diǎn)擊事件。
話說,你還在為按鈕addTarget:action:forControlEvents:然后找個別的地兒寫實現(xiàn)方法嗎?
[[self.testButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
NSLog(@"已經(jīng)實現(xiàn)點(diǎn)擊事件");
}];
- 監(jiān)聽通知
rac_addObserverForName
,如鍵盤<s>嘆氣</s>彈起的通知
[[[NSNotificationCenter defaultCenter]
rac_addObserverForName:UIKeyboardWillShowNotification object:nil]
subscribeNext:^(id x) {
NSLog(@"鍵盤彈出");
}];
- 手勢信號 之
rac_gestureSignal
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
[panGesture.rac_gestureSignal subscribeNext:^(id x) {
NSLog(@"拖動手勢:%@", x);
}];
[self.view addGestureRecognizer:panGesture];
- UITextView或UITextField文字變化信號 之
rac_textSignal
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"當(dāng)前文字:%@", x);
}];
第四:升級篇 知識進(jìn)階
如果你覺著上面的知識足矣用來開發(fā)項目,理論上可以簡化代碼量,但應(yīng)對更復(fù)雜的邏輯還是too young too naive,因此,接下來會聊到:
- RACSequence
- RACCommand
- RACSchedule
- RACChannel
- 信號轉(zhuǎn)換
- 信號拼接
- RAC常用宏
1. RACSequence
為了簡化OC語言中的集合類操作,發(fā)明了RACSequence。
原理:見ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽 中的RACSequence部分。
實現(xiàn),舉個栗子:
- (void)testRACSequence {
// 1.遍歷數(shù)組
NSArray *numbers = @[@1, @2, @3, @4];
// 這里其實是三步
// 第一步: 把數(shù)組轉(zhuǎn)換成集合RACSequence,即numbers.rac_sequence
// 第二步: 把集合RACSequence轉(zhuǎn)換RACSignal信號類,numbers.rac_sequence.signal
// 第三步: 訂閱信號,激活信號,會自動把集合中的所有值,遍歷出來。
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 2.遍歷字典,遍歷出來的鍵值對會包裝成RACTuple(元組對象)
NSDictionary *dict = @{@"name":@"ABC",@"age":@22};
// 下面RACTuple元組的概念請自行查閱
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元組,會把元組的值,按順序給參數(shù)里面的變量賦值
RACTupleUnpack(NSString *key,NSString *value) = x;
// 相當(dāng)于以下寫法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog(@"%@ %@", key, value);
}];
2. RACCommand
RACCommand是一個可以包裹一個信號,并對信號的執(zhí)行、完成、失敗狀態(tài)進(jìn)行把控的類。通常把異步網(wǎng)絡(luò)請求封裝成信號的形式,然后通過RACCommand包裹信號,實時反饋網(wǎng)絡(luò)請求狀態(tài)(請求中、請求成功、請求失敗)。
模擬網(wǎng)絡(luò)請求,舉個栗子:
/// 先在當(dāng)前VC中添加屬性:
@interface ViewController ()
@property (nonatomic, strong) RACCommand *command;
@end
/// 實現(xiàn)文件里寫入此示例代碼
- (void)testRACCommand {
if (!_command) {
_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"開始請求網(wǎng)絡(luò)");
// 假裝請求到數(shù)據(jù)
[subscriber sendNext:@"請求到的數(shù)據(jù)"];
// 請求完成
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"訂閱過程完成");
}];
}];
}];
// 監(jiān)控是否執(zhí)行中狀態(tài)
[_command.executing subscribeNext:^(id x) {
NSNumber *executing = (NSNumber *)x;
NSString *stateStr = executing.boolValue? @"請求狀態(tài)" : @"未請求狀態(tài)";
NSLog(@"%@", stateStr);
}];
// 監(jiān)控請求成功的狀態(tài)
[_command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"請求成功后,拿到數(shù)據(jù): %@", x);
}];
// 監(jiān)控請求失敗的狀態(tài)
[_command.errors subscribeNext:^(id x) {
NSLog(@"請求失敗: %@", x);
}];
}
// 執(zhí)行操作 調(diào)用上面createSignal中的block操作
[_command execute:nil];
}
打印結(jié)果為:
2017-06-27 17:10:21.672 RACDemo[65799:6318742] 未請求狀態(tài)
2017-06-27 17:10:21.680 RACDemo[65799:6318742] 請求狀態(tài)
2017-06-27 17:10:21.681 RACDemo[65799:6318742] 開始請求網(wǎng)絡(luò)
2017-06-27 17:10:21.681 RACDemo[65799:6318742] 請求成功后,拿到數(shù)據(jù): 請求到的數(shù)據(jù)
2017-06-27 17:10:21.682 RACDemo[65799:6318742] 訂閱過程完成
2017-06-27 17:10:21.682 RACDemo[65799:6318742] 未請求狀態(tài)
解讀
:
- 這里RACCommand的創(chuàng)建使用
initWithSignalBlock:
方法,其參數(shù)是一個“帶有一個參數(shù)并返回信號”的block,因此直接在后面返回一個信號即可。信號的創(chuàng)建方式和前文一致。 - 創(chuàng)建完RACCommand后,就可以用RACCommand的相關(guān)屬性看到當(dāng)前Signal的狀態(tài)。
- 不同于使用信號訂閱的三種狀態(tài)"
subscribeNext:
"、"subscribeError:
"和"subscribeCompleted
",這里用RACCommand的"executionSignals"屬性代表成功拿到發(fā)送的信號,"errors"屬性表示失敗的信號。 - 在執(zhí)行
[_command execute:nil];
前,RACCommand的"executing"屬性默認(rèn)是NO
,一旦執(zhí)行,"executing"屬性就會變成YES
狀態(tài),直到出現(xiàn)"sendCompleted
"或"sendError:
"情況時,"executing"屬性再次變成NO
。 - 由于RACCommand對信號執(zhí)行狀態(tài)多了更細(xì)致的描述,因此往往適用于異步的操作,如網(wǎng)絡(luò)請求這種需要在請求狀態(tài)時展示HUD、請求成功時隱藏HUD、請求成功的數(shù)據(jù)用來刷新視圖的場景,統(tǒng)統(tǒng)可以寫到一塊一并被處理。
- 這里的代碼只是模擬了網(wǎng)絡(luò)請求,真正開發(fā)時需要封裝現(xiàn)有的網(wǎng)絡(luò)請求框架,在請求到數(shù)據(jù)、請求失敗和請求成功時加入"
sendNext:
"、"sendError:
"和"sendCompleted
"代碼。
擴(kuò)展
:
這篇文章詳細(xì)介紹了RACCommand原理和能干的事兒,可以在了解完本篇后面的知識后閱讀學(xué)習(xí)。
ReactiveCocoa基本組件:理解和使用RACCommand
3. RACScheduler
RACScheduler是對GCD的一層封裝,管理信號的線程任務(wù),其原理建議參考這篇文章:
ReactiveCocoa 中 RACScheduler是如何封裝GCD的,注意其中對deliverOn方法的描述。
RACScheduler用法相對簡單,后面介紹信號拼接時會代碼提及。
4. RACChannel
原理篇請移步:RAC 中的雙向數(shù)據(jù)綁定 RACChannel
平時,主要用RACChannel的子類RACChannelTerminal來實現(xiàn)UI和屬性的雙向綁定功能,即UI變化屬性跟隨變化,屬性變化UI也跟隨變化。舉個栗子:
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *textField; // 輸入框
@property (nonatomic, copy) NSString *stringText; // 輸入框顯示的文字
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RACChannelTerminal *textFieldChannelT = self.textField.rac_newTextChannel;
// 輸入框文本變化反應(yīng)到stringText屬性上
RAC(self, stringText) = textFieldChannelT;
// stringText屬性對應(yīng)的文字訂閱到輸入框上 這樣就實現(xiàn)了雙向綁定
[RACObserve(self, stringText) subscribe:textFieldChannelT];
}
擴(kuò)展
:
系統(tǒng)框架還提供了以下可以返回RACChannelTerminal的方法:
- NSUserDefaults 之
rac_channelTerminalForKey:
- UIDatePicker 之
rac_newDateChannelWithNilValue:
- UISegmentedControl 之
rac_newSelectedSegmentIndexChannelWithNilValue:
- UISlider 之
rac_newValueChannelWithNilValue:
5. RAC常用宏
-
RAC(TARGET, [KEYPATH, [NIL_VALUE]])
常用于給某個對象的某個屬性綁定信號值
舉個栗子:
// 每當(dāng)textField文本變化時,就會把文本信息傳遞給self的textFieldText屬性
RAC(self, textFieldText) = [self.textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
-
RACObserve(TARGET, KEYPATH)
監(jiān)聽某個對象的某個屬性,一旦屬性值發(fā)生變化,立馬激活信號。
如前面KVO的例子:
[[self rac_valuesAndChangesForKeyPath:@"testNum"
options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"new value info is %@", x);
}];
使用此宏可以改寫為:
[RACObserve(self, testNum) subscribeNext:^(id x) {
NSLog(@"new value is %@", x);
}];
-
@weakify(obj)
和@strongify(obj)
為避免Block循環(huán)引用而生的一對宏
通常,blobk引用其外部變量,為避免持有外部對象,通常使用這一對宏來避免,如:
@weakify(self);
[RACObserve(self, testNum) subscribeNext:^(id x) {
@strongify(self);
NSLog(@"new value is %@", self.x);
}];
-
RACTurplePack
和RACTupleUnpack
分別代表打包生成元組 和 解包分解元組。
// RACTuplePack生成元組
RACTuple *tuple = RACTuplePack(@"數(shù)據(jù)1", @2);
// RACTupleUnpack分解元組 參數(shù):被解析的變量名
RACTupleUnpack(NSString *str, NSNumber *num) = tuple;
NSLog(@"%@ %@", str, num);
- RACChannelTo 雙向綁定終端
一般情況下,使用RACChannelTo(self, name) = RACChannelTo(self.model, name);即可完成雙向等值綁定功能,即一方變化另一方也跟隨變化,但前提是觸發(fā)屬性的KVO機(jī)制才能實現(xiàn)。
二般情況下,舉個栗子:
//首先在VC中添加兩個屬性:
@property (nonatomic, copy) NSString *valueA;
@property (nonatomic, copy) NSString *valueB;
// 然后添加測試方法
- (void)testRACChannelTo:(id)sender {
RACChannelTerminal *channelA = RACChannelTo(self, valueA);
RACChannelTerminal *channelB = RACChannelTo(self, valueB);
[[channelA map:^id(NSString *value) {
if ([value isEqualToString:@"西"]) {
return @"東";
}
return value;
}] subscribe:channelB];
[[channelB map:^id(NSString *value) {
if ([value isEqualToString:@"左"]) {
return @"右";
}
return value;
}] subscribe:channelA];
[[RACObserve(self, valueA) filter:^BOOL(id value) {
return value ? YES : NO;
}] subscribeNext:^(NSString* x) {
NSLog(@"你向%@", x);
}];
[[RACObserve(self, valueB) filter:^BOOL(id value) {
return value ? YES : NO;
}] subscribeNext:^(NSString* x) {
NSLog(@"他向%@", x);
}];
self.valueA = @"西";
self.valueB = @"左";
}
輸出結(jié)果為:
2017-06-30 17:19:50.125 RACDemo[78483:7464849] 你向西
2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向東
2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向左
2017-06-30 17:19:50.127 RACDemo[78483:7464849] 你向右
6. 信號轉(zhuǎn)換
-
map
轉(zhuǎn)換信號值,返回新信號
- (void)testMap {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"石"];
return nil;
}] map:^id(NSString* value) {
if ([value isEqualToString:@"石"]) {
return @"金";
}
return value;
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
輸出結(jié)果為:
2017-06-29 10:35:41.914 RACDemo[69180:6961894] 金
- filter 過濾
- (void)testFilter {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@(15)];
[subscriber sendNext:@(17)];
[subscriber sendNext:@(21)];
[subscriber sendNext:@(14)];
[subscriber sendNext:@(30)];
return nil;
}] filter:^BOOL(NSNumber* value) {
return value.integerValue >= 18;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
輸出結(jié)果為:
2017-06-29 10:39:13.718 RACDemo[69242:6964994] 21
2017-06-29 10:39:13.718 RACDemo[69242:6964994] 30
- flattenMap 直接轉(zhuǎn)成新的信號
- (void)testFlattenMap {
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"打蛋液");
[subscriber sendNext:@"蛋液"];
[subscriber sendCompleted];
return nil;
}] flattenMap:^RACStream *(NSString* value) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"把%@倒進(jìn)鍋里面煎",value);
[subscriber sendNext:@"煎蛋"];
[subscriber sendCompleted];
return nil;
}];
}] flattenMap:^RACStream *(NSString* value) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"把%@裝到盤里", value);
[subscriber sendNext:@"上菜"];
[subscriber sendCompleted];
return nil;
}];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
輸出結(jié)果為:
2017-06-29 11:02:05.035 RACDemo[69369:6975870] 打蛋液
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把蛋液倒進(jìn)鍋里面煎
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把煎蛋裝到盤里
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 上菜
- take 只取前幾個訂閱
- (void)testTake {
RACSubject *subject = [RACSubject subject];
//只取前兩次的信號值
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendNext:@"4"];
}
輸出結(jié)果為:
2017-06-29 11:41:22.418 RACDemo[69701:7001833] 1
2017-06-29 11:41:22.418 RACDemo[69701:7001833] 2
- takeLast 只取最后幾個訂閱
- (void)testTakeLast {
RACSubject *subject = [RACSubject subject];
//只取前兩次的信號值
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendNext:@"4"];
// 注意此處必須訂閱完成 才能使用takeLast進(jìn)行最后幾次訂閱的統(tǒng)計
[subject sendCompleted];
}
輸出結(jié)果為:
2017-06-29 11:48:15.642 RACDemo[69834:7008684] 3
2017-06-29 11:48:15.642 RACDemo[69834:7008684] 4
- skip 先跳過幾個訂閱
// 默認(rèn)UITextField創(chuàng)建時就能訂閱到其信號,往往會跳過第一次信號
[[self.textfield.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- ignore 內(nèi)部調(diào)用filter過濾,忽略掉ignore的值
- (void)testIgnore {
RACSubject *subject = [RACSubject subject];
//只取前兩次的信號值
[[subject ignore:@"2"] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendNext:@"4"];
}
輸出結(jié)果為:
2017-06-29 12:06:56.999 RACDemo[69907:7018889] 1
2017-06-29 12:06:57.000 RACDemo[69907:7018889] 3
2017-06-29 12:06:57.000 RACDemo[69907:7018889] 4
- distinctUntilChanged 只訂閱當(dāng)前信號值不同的信號
- (void)testDistinctUntilChanged {
RACSubject *subject = [RACSubject subject];
//當(dāng)上一次的值和這次的值有明顯變化的時候就會發(fā)出信號,否則會忽略掉
//一般用來刷新UI界面,當(dāng)數(shù)據(jù)有變化的時候才會刷新
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"A"];
[subject sendNext:@"A"];
[subject sendNext:@"B"];
}
輸出結(jié)果為:
2017-06-29 15:39:03.417 RACDemo[70120:7083874] A
2017-06-29 15:39:03.418 RACDemo[70120:7083874] B
- switchTolatest 獲取信號組中的信號
如上面RACCommand的代碼栗子中:
// 監(jiān)控請求成功的狀態(tài)
[_command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"請求成功后,拿到數(shù)據(jù): %@", x);
}];
這里的executionSignals返回的是信號組(本身也是信號類型), RACCommand每`excute:`一次就會向信號組中扔進(jìn)去新的信號,這里我們先拿到信號組,然后用switchToLatest屬性拿到這個最新的信號,最后再進(jìn)行訂閱。
7. 信號拼接
- concat 拼接。注意:在signalA后拼接signalB,只有signalA發(fā)送完成
sendCompleted
,signalB才會被拼接。
- (void)testConcat {
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"我戀愛啦"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"我結(jié)婚啦"];
[subscriber sendCompleted];
return nil;
}];
[[signalA concat:signalB] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
輸出結(jié)果為:
2017-06-30 10:47:36.654 RACDemo[75454:7236811] 我戀愛啦
2017-06-30 10:47:36.655 RACDemo[75454:7236811] 我結(jié)婚啦
- then 轉(zhuǎn)接。連接下一個信號,當(dāng)前信號完成后,才連接then后的信號。
- (void)testThen {
// then:用于連接兩個信號,當(dāng)?shù)谝粋€信號完成,才會連接then返回的信號
// 注意使用then,之前信號的值會被忽略掉.
// 底層實現(xiàn):1、先過濾掉之前的信號發(fā)出的值。2.使用concat連接then返回的信號
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
// 這里必須sendCompleted才能執(zhí)行then后的信號
[subscriber sendCompleted];
return nil;
}] then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@2];
return nil;
}];
}] subscribeNext:^(id x) {
// 只能接收到第二個信號的值,也就是then返回信號的值
NSLog(@"%@",x);
}];
}
輸出結(jié)果為:
2017-06-30 10:44:08.578 RACDemo[75394:7233518] 2
- merge 合并。任何一個信號有新值的時候就會被訂閱。
- (void)testMerge {
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"紙廠污水"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"電鍍廠污水"];
return nil;
}];
[[RACSignal merge:@[signalA, signalB]] subscribeNext:^(id x) {
NSLog(@"處理%@",x);
}];
}
輸出結(jié)果為:
2017-06-30 10:55:58.645 RACDemo[75697:7244204] 處理紙廠污水
2017-06-30 10:55:58.645 RACDemo[75697:7244204] 處理電鍍廠污水
- zip(同步合并壓縮) + reduce(分解元組)
- (void)testZip {
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *zipSignal = [RACSignal zip:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
return [letter stringByAppendingString:number];
}];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[numbers sendNext:@"3"];
[letters sendNext:@"C"];
[letters sendNext:@"D"];
[numbers sendNext:@"4"];
}
輸出結(jié)果為:
2017-06-30 11:03:50.907 RACDemo[75837:7251093] A1
2017-06-30 11:03:50.908 RACDemo[75837:7251093] B2
2017-06-30 11:03:50.908 RACDemo[75837:7251093] C3
2017-06-30 11:03:50.908 RACDemo[75837:7251093] D4
/// zip后發(fā)出的信號值是元組類型,因此這里使用 reduce: 方法直接分解元素,拿到元組中對應(yīng)的值
/// zip的合并行為是按順序取出各個信號然后合并發(fā)出的
/// 也就是說,letters的第一個值A(chǔ)和number的第一個值1合并輸出A1,第二個值B和number的第二個值2合并輸出B2
/// 假設(shè)D后面,還有E,F,G...,但是沒有對應(yīng)的number信號,zip的合并行為就無法進(jìn)行下去了.
- combineLatest(最新值合并壓縮) + reduce(分解元組)
- (void)testCombineLatest{
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal combineLatest:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
return [letter stringByAppendingString:number];
}];
[combined subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];
[letters sendNext:@"D"];
[numbers sendNext:@"4"];
}
輸出結(jié)果為:
2017-06-30 11:12:59.470 RACDemo[75910:7256870] B1
2017-06-30 11:12:59.471 RACDemo[75910:7256870] B2
2017-06-30 11:12:59.471 RACDemo[75910:7256870] C2
2017-06-30 11:12:59.471 RACDemo[75910:7256870] C3
2017-06-30 11:12:59.471 RACDemo[75910:7256870] D3
2017-06-30 11:12:59.472 RACDemo[75910:7256870] D4
/// combineLatest后發(fā)出的信號值是元組類型,因此這里使用 reduce: 方法直接分解元素,拿到元組中對應(yīng)的值
/// 上面代碼可以發(fā)現(xiàn):letter信號的A值被更新的B值覆蓋了,所以接下來接收到number信號的1時候,合并,輸出信號B1.
/// 當(dāng)接收到C的時候,與number的最新的值2合并,輸出信號C2.
- takeUntil 獲取信號直到某個信號開始執(zhí)行
- (void)testTakeUntil {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
[subscriber sendNext:@"直到世界的盡頭才能把我們分開"];
}];
return nil;
}] takeUntil:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"世界的盡頭到了");
[subscriber sendNext:@"世界的盡頭到了"];
});
return nil;
}]] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
輸出結(jié)果為:
2017-06-30 11:46:55.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:56.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:57.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:58.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:59.102 RACDemo[75983:7272054] 世界的盡頭到了
- doNext、doCompleted、doError 分別在sendNext、sendConpleted和sendError前執(zhí)行操作
- (void)doNextOrComleted {
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 執(zhí)行[subscriber sendNext:@1];之前會調(diào)用這個Block
NSLog(@"doNext");;
}] doCompleted:^{
// 執(zhí)行[subscriber sendCompleted];之前會調(diào)用這個Block
NSLog(@"doCompleted");;
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
輸出結(jié)果為:
2017-06-30 11:54:39.750 RACDemo[76163:7277651] doNext
2017-06-30 11:54:39.751 RACDemo[76163:7277651] 1
2017-06-30 11:54:39.751 RACDemo[76163:7277651] doCompleted
- rac_liftSelector 每個信號至少sendNext一次后,拿到每個信號最新的信號值,然后執(zhí)行selector中的操作
- (void)testLiftSelector {
// 當(dāng)多次數(shù)據(jù)請求時,需要全部請求結(jié)束之后才會進(jìn)行下一步UI刷新等,可以用這個rac_liftSelector
// 第一部分?jǐn)?shù)據(jù)
RACSignal *section01Signal01 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"section01數(shù)據(jù)請求");
[subscriber sendNext:@"section01請求到的數(shù)據(jù)"];
return nil;
}];
// 第二部分?jǐn)?shù)據(jù)
RACSignal *section01Signal02 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"section02數(shù)據(jù)請求");
[subscriber sendNext:@"section02請求到的數(shù)據(jù)"];
return nil;
}];
// 第三部分?jǐn)?shù)據(jù)
RACSignal *section01Signal03 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"section03數(shù)據(jù)請求");
[subscriber sendNext:@"section03請求到的數(shù)據(jù)"];
return nil;
}];
// 可以在所有信號請求完成之后再執(zhí)行方法,只需把三個結(jié)果傳出去即可
// 注意下面@selector中的參數(shù)數(shù)量和傳遞和信號數(shù)量是一致的
[self rac_liftSelector:@selector(refreshUI:::) withSignals:section01Signal01,section01Signal02,section01Signal03, nil];
}
- (void)refreshUI:(NSString *)str1 :(NSString *)str2 :(NSString *)str3 {
NSLog(@"最終數(shù)據(jù)為:\n%@\n%@\n%@", str1, str2, str3);
}
輸出結(jié)果為:
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section01數(shù)據(jù)請求
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section02數(shù)據(jù)請求
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section03數(shù)據(jù)請求
2017-06-30 12:07:43.383 RACDemo[76282:7286396] 最終數(shù)據(jù)為:
section01請求到的數(shù)據(jù)
section02請求到的數(shù)據(jù)
section03請求到的數(shù)據(jù)
8. 信號時間或頻率控制
- timeOut 超時。超時一定時間后自動sendError
- (void)testTimeOut {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"100"];
return nil;
}] timeout:1 onScheduler: [RACScheduler currentScheduler]]; // 這里將時間操作放在當(dāng)前線程中執(zhí)行
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"1秒后會自動調(diào)用");
}];
}
輸出結(jié)果為:
2017-06-30 12:20:41.715 RACDemo[76418:7295742] 100
2017-06-30 12:20:42.812 RACDemo[76418:7295742] 1秒后會自動調(diào)用
- delay 延遲。延遲一定時間后再進(jìn)行信息傳遞
- (void)testDelay {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"等等我,我還有3秒鐘就到了");
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}] delay:3] subscribeNext:^(id x) {
NSLog(@"我到了");
}];
}
輸出結(jié)果為:
2017-06-30 12:27:15.584 RACDemo[76593:7301745] 等等我,我還有3秒鐘就到了
2017-06-30 12:27:18.884 RACDemo[76593:7301745] 我到了
- replay 同RACReplaySubject實現(xiàn)原理一樣,將要傳遞的值打包保存一次,然后依次派送給訂閱者。
- (void)testReplay {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}] replay]; // 使用replay后上面block中的代碼只執(zhí)行一次
[signal subscribeNext:^(id x) {
NSLog(@"第一個訂閱者%@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"第二個訂閱者%@",x);
}];
}
輸出結(jié)果為:
2017-06-30 14:39:10.989 RACDemo[76768:7350292] 第一個訂閱者1
2017-06-30 14:39:11.778 RACDemo[76768:7350292] 第一個訂閱者2
2017-06-30 14:39:12.394 RACDemo[76768:7350292] 第二個訂閱者1
2017-06-30 14:39:13.068 RACDemo[76768:7350292] 第二個訂閱者2
- retry 錯誤重試。訂閱者sendError后重新執(zhí)行信號block操作
- (void)testRetry {
__block int failedCount = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (failedCount < 5) {
failedCount++;
NSLog(@"我失敗了");
[subscriber sendError:nil];
}else{
NSLog(@"經(jīng)歷了五次失敗后");
[subscriber sendNext:nil];
}
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"終于成功了");
}];
}
輸出結(jié)果為:
2017-06-30 14:47:19.473 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.474 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 經(jīng)歷了五次失敗后
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 終于成功了
另外:
- (RACSignal *)retry:(NSInteger)retryCount; 方法可以控制重試的次數(shù)。
- interval 定時。每隔一段時間激活一次信號
- (void)testInterval {
__block int num = 0;
[[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
num++;
NSLog(@"count:%d", num);
}];
}
輸出結(jié)果為:
2017-06-30 14:57:02.553 RACDemo[77081:7365782] count:1
2017-06-30 14:57:03.553 RACDemo[77081:7365782] count:2
2017-06-30 14:57:04.553 RACDemo[77081:7365782] count:3
2017-06-30 14:57:05.553 RACDemo[77081:7365782] count:4
2017-06-30 14:57:06.553 RACDemo[77081:7365782] count:5
···
- throttle 節(jié)流。一定時間內(nèi)不接收任何信號內(nèi)容,一旦到達(dá)這個時間就獲取最新最近的信號內(nèi)容。
- (void)testThrottle {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"旅客A"];
// 1秒時發(fā)送一條信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"旅客B"];
});
// 1.5秒時發(fā)送一條信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"插隊旅客"];
});
// 2秒時發(fā)送三條信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"旅客C"];
[subscriber sendNext:@"旅客D"];
[subscriber sendNext:@"旅客E"];
});
// 3秒時發(fā)送一條信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"旅客F"];
});
return nil;
}] throttle:1] subscribeNext:^(id x) {
NSLog(@"%@通過了",x);
}];
}
輸出結(jié)果為:
2017-06-30 15:10:44.948 RACDemo[77224:7375779] 旅客A通過了
2017-06-30 15:10:46.947 RACDemo[77224:7375779] 旅客E通過了
2017-06-30 15:10:48.046 RACDemo[77224:7375779] 旅客F通過了
第五:MVVM + ReactiveCocoa
實戰(zhàn)開發(fā)中,ReactiveCocoa通常和MVVN設(shè)計模式結(jié)合使用,最快讓你上手ReactiveCocoa之進(jìn)階篇 后半部分已經(jīng)對其做了示例介紹,就不再贅述。
希望本文章可以作為代碼及原理查詢手冊,方便開發(fā)使用。
最后,貼出兩張速查表(第二張圖在簡書上放大也看不清,建議下載后放大查看),以示敬意~
參考鏈接:
最快讓你上手ReactiveCocoa之基礎(chǔ)篇
ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽
ReactiveCocoa基本組件:理解和使用RACCommand
細(xì)說 ReactiveCocoa 的冷信號與熱信號(三):怎么處理冷信號與熱信號
ReactiveCocoa源碼閱讀之RACScheduler
RAC 中的雙向數(shù)據(jù)綁定 RACChannel
iOS ReactiveCocoa 最全常用API整理(可做為手冊查詢)
ReactiveCocoa 中 RACScheduler是如何封裝GCD的
RAC核心元素與信號流
這樣好用的ReactiveCocoa,根本停不下來
ReactiveCocoa操作方法(過濾,秩序,時間,重復(fù))
最快讓你上手ReactiveCocoa之進(jìn)階篇
RAC中combineLatest,zip,sample的測試
RACSignal實踐