由於公司項目的緣由,開始接觸MVVM+RAC的這種模式,剛開始並非很適應這種函數式響應式的編程思想,感受使用起來很是繁瑣,大大的增長了開發的負擔.可是隨着本身學習的深刻和項目的實踐,這種模式的優勢也隨之顯現.因此寫這篇文章但願記錄本身學習的過程,若是有寫的不對的地方也但願你們指正.編程
本篇文章主要針對的是Objective-C語言來說解ReactiveCocoa的應用,使用的也是公認最穩定的ReactiveCocoa v2.5,ReactiveCocoa在3.0之後的版本就是針對Swift的版本,因此你們能夠根據本身須要來作下載.json
你們都知道MVC是iOS App推薦的用來組織代碼的權威規範,大部分的App也都遵循這樣的構建,可是這樣的設計模式卻會隨着項目的不斷髮展,業務邏輯的不斷複雜讓Controller變得臃腫,使得MVC從Model View Controller變成了Massive View Controller,這時傳統的MVC設計模式已經不能知足咱們的需求.而MVVM的出現極大的解決了這一問題,他是MVC的進一步發展,將Controller裏面的業務邏輯所有抽離到ViewModel裏面,咱們只須要在Controller裏面處理邏輯的回調結果便可.設計模式
固然MVVM使咱們的Controller完成了瘦身,可是ViewModel的出現,也使得咱們須要在Controller中引入ViewModel這個類,使得咱們所管理的類又多了一個,之間的交互就變得更加的麻煩.此時RAC的出現就正好接管這一套邏輯上的交互,用「信號流」的概念使得邏輯變得扁平化,咱們只要關心「信號流」的流向便可.數組
RAC 中最核心的概念之一就是信號RACStream,RACStream中包含的兩個子類——RACSignal 和 RACSequence.由於本篇文章只是介紹RAC在實戰中的用法,因此會以RACSignal來介紹(好吧,實際上是由於筆者瞭解的太淺了-u-).若是想知道具體內部實現能夠去看下霜神關於RAC源碼解讀的文章.網絡
不說廢話了,直接開幹,首先來看一段咱們常見的signal建立->訂閱->銷燬信號的整個流程代碼.併發
//建立信號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
//發送信號
[subscriber sendNext:@"啊哈啦啦啦"];
[subscriber sendCompleted];
//取消訂閱 能夠選擇在此作資源釋放的操做
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"subscribe value = %@",x);
//輸出結果: subscribe value = 啊哈啦啦啦
}];
//取消訂閱
[disposable dispose];
複製代碼
內部邏輯實現: (1):RACSingal調用createSignal:建立信號,內部會去調用其子類RACDynamicSignal去建立信號. (2):RACDynamicSignal調用createSignal:方法,後面惟一的參數是個叫didSubscribe的block,當執行sendNext發送信號時,會將發送的內容保存在didSubscribe的block中. (3):signal信號執行subscribeNext方法,會把以前保存在didSubscribe的內容取出來. (4):取消訂閱,執行disposableWithBlock這個block.函數
這樣RACSignal的建立->訂閱->銷燬信號的一整個流程代碼就完成了.這種只有當訂閱者完成了訂閱纔會發送信號,因此咱們稱其爲冷信號.他就像是在一條生產線上,打開了機器,可是這個時候沒有工人上班,那麼工廠也不會正常運做.學習
經過查看源碼咱們發現RACSubject是繼承自RACSignal的一個子類,而且遵循了協議,意味着它既能夠訂閱信號,也能發送信號.atom
RACSubject的例子應用spa
//調用subject方法建立信號
複製代碼
RACSubject *subject = [RACSubject subject]; //訂閱信號 [subject subscribeNext:^(id x) { NSLog(@"x = %@",x); }]; //發送信號 [subject sendNext:@"啊哈啦啦啦"];
內部實現邏輯: (1):調用subject方法,建立信號.內部建立一個_subscribers可變數組,用來存儲訂閱信號的訂閱者. (2):調用sendNext方法,發送消息.這時內部調用enumerateSubscribersUsingBlock方法對訂閱者進行遍歷,併發送消息. (3):全部訂閱過改subject信號的訂閱者會收到此消息,並完成打印x內容.
到這裏RACSubject的一整套流程就完成了. RACSubject中無論有沒有信號被訂閱它都會去發送消息,這種特性的信號咱們稱之爲熱信號.就比如工廠裏的生產線一直在運做,有工人訂閱了就會用數組存起來,等到有任務(消息)下發了,就會去執行這個任務.
查看源碼咱們知道,RACCommand和以前的「信號流」概念不太同樣,它是一個繼承自NSObject的類,它的主要目的是爲了管理和訂閱RACSignal的類.在咱們作UI組件交互的時候, RACCommand可以幫助咱們更快的處理業務,下降代碼的複雜度,節省開發的時間.
使用場景:監聽按鈕的點擊事件、網絡請求與回調處理.
知道了RACCommand的用途和使用場景,爲了更好的理解RACCommand,咱們先來看看RACCommand的兩個初始化方法和執行方法:
初始化方法:
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
複製代碼
執行方法:
- (RACSignal *)execute:(id)input;
- (void)setRac_command:(RACCommand *)command;
複製代碼
方法介紹: 1:咱們知道RACCommand的做用是管理RACSignal的信號,因此初始化方法的signalBlock的返回類型就是咱們須要管理的RACSignal,他的入參input,就是咱們執行該Command時所傳入的數據. 2:初始化第二個方法較第一個方法多了個RACSignal類型的參數enabledSignal;這個參數的目的主要是爲了過濾信號,只有當該信號中傳遞的參數爲真時, Command纔可以被執行. 3:執行方法中第一個方法是RACCommand裏面用於執行的方法,直接調用便可. 4:執行方法中第二個方法是UIButton的分類方法,具體使用後面會作介紹.
知道了MVVM、RACSingal、RACSubject、RACCommand的介紹和用法,接下來咱們就能夠在實際項目中進行應用了.
首先咱們須要在ViewModel.h文件中聲明一個command,用於管理咱們的信號.
//聲明屬性testCommand
@property (nonatomic, strong) RACCommand *testCommand;
複製代碼
注意:這裏聲明的testCommand必須使用strong修飾強引用,不然接受不到RACCommand內部的信號.
而後ViewModel.m文件中在get方法中進行初始化操做.
//testCommand
- (RACCommand *)testCommand
{
if (!_testCommand) {
_testCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//須要傳遞的參數,若是傳入的input就是網絡請求須要的參數,直接傳input便可
NSDictionary *sendParams = @{@"test":@"我是啊哈啦啦啦"};
//在這裏面進行網絡請求的操做,筆者本身把網絡請求封裝成了一個信號,方便訂閱處理.
[[YWApiManager sendApi:SCApiTypeTest withParam:sendParams] subscribeNext:^(NSDictionary *json{
//json:是網絡請求回調後,轉換後取得的json
[subscriber sendNext:json];
//必定要加上sendCompleted這個方法,否則沒法再次執行該command
[subscriber sendCompleted];
} error:^(NSError *error) {
//錯誤信息 sendError 內部已經取消訂閱信號 不用執行sendCompleted方法
[subscriber sendError:error];
}];
return nil;
}];
}];
}
return _testCommand;
}
複製代碼
上面的方法中,筆者直接採用了initWithSignalBlock這個方法初始化RACCommand,若是說你在執行方法時已將須要須要傳遞的參數字典傳入,那麼能夠直接將input當成sendParams傳入. 注意:在發送消息後,必定要執行[subscriber sendCompleted]; 表示發送消息已經結束,取消信號的訂閱.否則的話該command會一直處於執行中,不能再次執行該command.
寫到這裏 已經成功的將咱們Controller中的網絡請求和回調處理好了,接下來咱們須要在Controller裏面對信號發送的json進行處理,看下面Controller中的代碼.
首先咱們須要在Controller中導入ViewModel,而且聲明對象viewModel.具體操做看下面的代碼
[self.viewModel.testCommand.executionSignals subscribeNext:^(RACSignal * _Nullable execution) {
[execution subscribeNext:^(id x) {
//x爲網絡請求的回調結果,能夠在這裏對數據進行處理
NSLog(@"json = %@",x);
}];
}];
複製代碼
使用testCommand的executionSignals信號進行訂閱操做. executionSignals是一個內部裝有RACSignal的高階信號,因此咱們對他進行降階操做拿到execution信號,並再次訂閱此信號,此時入參的x就是咱們以前傳遞的網絡請求回調「json」.
若是咱們在非併發RACCommand中咱們能夠用switchToLatest進行降階操做,這樣寫比較直觀,也是筆者在項目中經常使用的方法.
[self.viewModel.testCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
//x爲網絡請求的回調結果,能夠在這裏對x作處理,修改UI
NSLog(@"json = %@",x);
}];
複製代碼
對於錯誤信號的訂閱:
[self.viewModel.testCommand.errors subscribeNext:^(NSError *error) {
NSLog(@"error = %@",error);
}];
複製代碼
注意:咱們不該該使用subscribeError:這個方法取訂閱錯誤信號,由於executionSignals這個信號是不會發送error事件的.因此需使用subscribeNext:訂閱錯誤信號.
最後咱們只要執行該方法就好了,執行代碼地方傳的參數能夠爲空,或者傳入須要用到的參數,這個能夠根據需求本身來決定.
[self.viewModel.testCommand execute:@"啊哈啦啦啦"];
複製代碼
而後咱們再來看看第二種執行方法,這種方法會使按鈕綁定上testCommand,若是RACCommand是以initWithEnabled這種方式初始化的,按鈕的enabled屬性會隨enabledSignal傳入的值的改變而改變.即傳入值爲真,按鈕不可點擊.
testButton.rac_command = self.viewModel.testCommand;
複製代碼
以上代碼都是針對冷信號來處理,讓咱們在看下RACSubject在項目中的用法.
應用場景:好比如今咱們有個tableView的列表,每一個cell的點擊事件跳到新的界面,在新的界面中咱們會選擇一些數據並回傳到以前的界面,最後刷新tableView,把選擇的數據展現在tableView上.這樣的一個操做咱們就可使用RACSubject來完成,看下面代碼.
@property (nonatomic, strong) RACSubject *reloadSignal;
複製代碼
首先我在ViewModel裏面聲明瞭一個熱信號reloadSignal,而後初始化testCommand.
//testCommand
- (RACCommand *)testCommand
{
if (!_testCommand) {
@weakify(self);
_testCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"我是阿哈啦啦啦,我要被髮送了"];
[subscriber sendCompleted];
return nil;
}]doNext:^(id x) {
@strongify(self);
//doNext的入參x是sendNext發送的參數
[self.reloadSignal sendNext:x];
}];
}];
}
return _testCommand;
}
複製代碼
和第一個例子不一樣的是,這裏發送的數據會進行一步操做,調用doNext:方法(將sendNext的參數傳遞給doNext的入參).而後熱信號reloadSignal發送入參x.
最後在Controller中的操做,和例1中是同樣的,要注意的是調用時須要使用reloadSignal進行訂閱.熱信號的優勢在於,對於須要進行屢次reload的這種操做,咱們不用去重複訂閱.
[self.viewModel.reloadSignal subscribeNext:^(id x) {
NSLog(@"x = %@",x);
}];
複製代碼
關於RAC這塊筆者本身還在學習之中,因此但願拋磚引玉,你們互相討論共同進步.以上就是關於ReactiveCocoa的一個簡單用法,比較簡單實用,但願能幫到新學習RAC的各位.