iOS開發(fā)之ReactiveCocoa下的MVVM(干貨分享)

轉(zhuǎn)載:http://ios.jobbole.com/83602/

最近工作比較忙,但還是出來更新博客了,今天給大家分享一些ReactiveCocoa以及MVVM的一些東西,干活還是比較足的。在之前發(fā)表過一篇博文,名字叫做《iOS開發(fā)之淺談MVVM的架構(gòu)設(shè)計(jì)與團(tuán)隊(duì)協(xié)作》,大體上講的就是使用Block回調(diào)的方式實(shí)現(xiàn)MVVM的。在寫上篇文章時(shí)也知道有ReactiveCocoa這個(gè)函數(shù)響應(yīng)式編程的框架,并且有許多人用它來更好的實(shí)現(xiàn)MVVM。所以在上篇博客發(fā)表后,有些同行給評(píng)論建議看一下ReactiveCocoa的東西,所以就系統(tǒng)的看了一下ReactiveCocoa的東西。不過有一點(diǎn)要說明的就是,不使用ReactiveCocoa是可以實(shí)現(xiàn)MVVM的,并非使用MVVM模式你就必須的使用ReactiveCocoa的東西,你可以使用KVO,Block,Delegate,Navigation等手段,而ReactiveCocoa更優(yōu)雅的實(shí)現(xiàn)了這個(gè)過程。ReactiveCocoa就是一個(gè)響應(yīng)式編程的框架,它會(huì)使MVVM每層之間交互起來更為方便,所以長和MVVM聯(lián)系在一起。

一.函數(shù)響應(yīng)式編程(Function Reactive Programming)

關(guān)于函數(shù)響應(yīng)式編程的東西,我想引用國外這個(gè)ReactiveCocoa教學(xué)視頻(視頻鏈接https://vimeo.com/65637501)中的一張PPT來簡單的說一下什么是函數(shù)響應(yīng)式編程。那就直接上圖,下圖是上方視頻鏈接的截圖,很形象的解釋了什么是函數(shù)響應(yīng)式編程。簡單的說下方c = a + b 定義好后,當(dāng)a的值變化后,c的值就會(huì)自動(dòng)變化。不過a的值變化時(shí)會(huì)產(chǎn)生一個(gè)信號(hào),這個(gè)信號(hào)會(huì)通知c根據(jù)a變化的值來變化自己的值。b的值變化同樣也影響c的值。下圖很好的表達(dá)了這個(gè)思想。在此就不做贅述了。

二. ReactiveCocoa簡介

先簡單的介紹一下什么是ReactiveCocoa框架,然后通過實(shí)例好好的去搞一搞這個(gè)框架,最后就是如何在項(xiàng)目中使用了。關(guān)于

ReactiveCocoa的理解一些博客(見本篇博客中的鏈接分享)中把ReactiveCocoa比喻成管道,ReactiveCocoa中的

Signal就是管道中的水流。使用ReactiveCocoa可以方便的在MVVM各層之間架起溝通的管道,便于每層之間的交互。現(xiàn)在在我們做的工程中

已經(jīng)在使用ReactiveCocoa框架了,用起來的感覺是非常爽的,好用!

可以說ReactiveCocoa中核心是信號(hào)量機(jī)制,Signal在ReactiveCocoa中發(fā)揮著強(qiáng)大的不可代替的作用,可謂是

ReactiveCocoa的靈魂所。Signal是可以攜帶一些對(duì)象和參數(shù)的,你可以獲取該對(duì)象并且可以對(duì)該信號(hào)量攜帶的值進(jìn)行map,

filter等常用操作,操作后的值會(huì)和該信號(hào)量進(jìn)行綁定。先簡單的這么一說,后邊的部分回詳細(xì)的介紹如何讓信號(hào)量發(fā)揮強(qiáng)大的作用。

ReactiveCocoa中對(duì)Block的使用可謂是淋漓盡致,如果對(duì)Block使用不熟的朋友可以補(bǔ)一下Block的東西,然后在回頭看一下ReactiveCocoa的東西。關(guān)于ReactiveCocoa更多的東西,請參考Github上的鏈接(https://github.com/ReactiveCocoa/ReactiveCocoa)。

三. 在工程中引入ReactiveCocoa

1.你可以使用Github上的加入方式如下所示,本人感覺比較麻煩,就沒有使用,采用的第二種方法(CocoaPods)。

2.上面的步驟難免有些麻煩,所以用CocoaPods更為便捷一些,Profile文件中的內(nèi)容如下所示,我用的是2.5版本。3.0后就支持

Swift了,因?yàn)槲覜]有用Swift寫東西,所以就用的是2.5版本,設(shè)置完P(guān)rofile文件后,pod install即可。

你可以pod search ReactiveCocoa看一下版本,選擇你需要的版本即可。

四.使用ReactiveCocoa

下方會(huì)通過一些簡單的實(shí)例來介紹一下信號(hào)量機(jī)制和一些常用的方法。

1.引入相應(yīng)的頭文件

在工程中引入下方的頭文件(建議在Pch文件中引入)就可以使用我們的ReactiveCocoa框架了

Objective-C

#import

#import

1

2#import

#import

2. Sequence和Map

Sequence:隊(duì)列,是ReactiveCocoa中引入的一個(gè)類型,它類似于數(shù)組,我們可以暫且把Sequence看做綁定信號(hào)

量的數(shù)組吧。在OC中的NSArray可以通過rac_sequence方法轉(zhuǎn)換成ReactiveCocoa中的Sequence,然后就可以調(diào)用處理

信號(hào)的一些方法了。

參考以下實(shí)例代碼:

(1)把NSArray通過rac_sequence方法生成RAC中的Sequence

(2)獲取該Sequence對(duì)象的信號(hào)量

(3)調(diào)用Signal的Map方法,使每個(gè)元素的首字母大寫

(4)通過subscribNext方法對(duì)其進(jìn)行遍歷輸出

Objective-C

//uppercaseString use map

- (void)uppercaseString {

RACSequence *sequence = [@[@"you", @"are", @"beautiful"] rac_sequence];

RACSignal *signal =? sequence.signal;

RACSignal *capitalizedSignal = [signal map:^id(NSString * value) {

return [value capitalizedString];

}];

[signal subscribeNext:^(NSString * x) {

NSLog(@"signal --- %@", x);

}];

[capitalizedSignal subscribeNext:^(NSString * x) {

NSLog(@"capitalizedSignal --- %@", x);

}];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19//uppercaseString use map

-(void)uppercaseString{

RACSequence*sequence=[@[@"you",@"are",@"beautiful"]rac_sequence];

RACSignal*signal=sequence.signal;

RACSignal*capitalizedSignal=[signalmap:^id(NSString*value){

return[valuecapitalizedString];

}];

[signalsubscribeNext:^(NSString*x){

NSLog(@"signal --- %@",x);

}];

[capitalizedSignalsubscribeNext:^(NSString*x){

NSLog(@"capitalizedSignal --- %@",x);

}];

}

下方截圖是上個(gè)這個(gè)方法中的運(yùn)行結(jié)果,從運(yùn)行結(jié)果不難看出,通過Signal相應(yīng)的方法處理完后,處理的結(jié)果會(huì)與新返回的信號(hào)量所綁定。原信號(hào)量中的值保持不變。每次信號(hào)量調(diào)用相應(yīng)的方法處理完數(shù)據(jù)后,都會(huì)返回一個(gè)新的信號(hào)量,而這個(gè)信號(hào)量是獨(dú)立于原信號(hào)量的。

由上面的介紹可知,上面方法中的一坨代碼可以寫成下方的一串。因?yàn)橐粋€(gè)方法調(diào)用后會(huì)返回一個(gè)持有新結(jié)果的新的信號(hào)量,然后在這個(gè)信號(hào)量的基礎(chǔ)上再次

調(diào)用信號(hào)量其他的方法。Signal還有其他一些好用的方法,用法和map方法類似,在此就不一一贅述了,gitHub上有相應(yīng)的實(shí)例文檔。

Objective-C

- (void)uppercaseString {

[[[@[@"you", @"are", @"beautiful"] rac_sequence].signal

map:^id(NSString * value) {

return [value capitalizedString];

}] subscribeNext:^(id x) {

NSLog(@"capitalizedSignal --- %@", x);

}];

}

1

2

3

4

5

6

7

8

9-(void)uppercaseString{

[[[@[@"you",@"are",@"beautiful"]rac_sequence].signal

map:^id(NSString*value){

return[valuecapitalizedString];

}]subscribeNext:^(idx){

NSLog(@"capitalizedSignal --- %@",x);

}];

}

3.信號(hào)量開關(guān)(Switch)

上面把信號(hào)量比喻成水管,那么Switch就是水龍頭呢。通過Switch我們可以控制那個(gè)信號(hào)量起作用,并且可以在信號(hào)量之間進(jìn)行切換。也可以這

么理解,把Switch看成另一段水管,Switch對(duì)接那個(gè)水管,就流那個(gè)水管的水,這樣比喻應(yīng)該更為貼切一些。下方是一個(gè)關(guān)于Switch的一個(gè)小實(shí)

例。

(1) 首先創(chuàng)建3個(gè)自定義信號(hào)量(3個(gè)水管),前兩個(gè)水管是用來接通不同的水源的(google, baidu),

而最后一個(gè)信號(hào)量是用來對(duì)接不同水源水管的水管(signalOfSignal)。signalOfSignal接baidu水管上,他就流baidu水

源的水,接google水管上就流google水源的水。

(2) 把signalOfSignal信號(hào)量通過switchToLatest方法加工成開關(guān)信號(hào)量。

(3) 緊接著是對(duì)通過開關(guān)數(shù)據(jù)進(jìn)行處理。

(4) 開關(guān)對(duì)接baidu信號(hào)量,然后baidu和google信號(hào)量同時(shí)往水管里灌入數(shù)據(jù),那么起作用的是baidu信號(hào)量。

(5) 開關(guān)對(duì)接google信號(hào)量,google和baidu信號(hào)量發(fā)送數(shù)據(jù),則google信號(hào)量輸出到signalOfSignal中

Objective-C

//信號(hào)開關(guān)Switch

- (void)signalSwitch {

//創(chuàng)建3個(gè)自定義信號(hào)

RACSubject *google = [RACSubject subject];

RACSubject *baidu = [RACSubject subject];

RACSubject *signalOfSignal = [RACSubject subject];

//獲取開關(guān)信號(hào)

RACSignal *switchSignal = [signalOfSignal switchToLatest];

//對(duì)通過開關(guān)的信號(hào)量進(jìn)行操作

[[switchSignal? map:^id(NSString * value) {

return [@"https//www." stringByAppendingFormat:@"%@", value];

}] subscribeNext:^(NSString * x) {

NSLog(@"%@", x);

}];

//通過開關(guān)打開baidu

[signalOfSignal sendNext:baidu];

[baidu sendNext:@"baidu.com"];

[google sendNext:@"google.com"];

//通過開關(guān)打開google

[signalOfSignal sendNext:google];

[baidu sendNext:@"baidu.com/"];

[google sendNext:@"google.com/"];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27//信號(hào)開關(guān)Switch

-(void)signalSwitch{

//創(chuàng)建3個(gè)自定義信號(hào)

RACSubject*google=[RACSubjectsubject];

RACSubject*baidu=[RACSubjectsubject];

RACSubject*signalOfSignal=[RACSubjectsubject];

//獲取開關(guān)信號(hào)

RACSignal*switchSignal=[signalOfSignalswitchToLatest];

//對(duì)通過開關(guān)的信號(hào)量進(jìn)行操作

[[switchSignalmap:^id(NSString*value){

return[@"https//www."stringByAppendingFormat:@"%@",value];

}]subscribeNext:^(NSString*x){

NSLog(@"%@",x);

}];

//通過開關(guān)打開baidu

[signalOfSignalsendNext:baidu];

[baidusendNext:@"baidu.com"];

[googlesendNext:@"google.com"];

//通過開關(guān)打開google

[signalOfSignalsendNext:google];

[baidusendNext:@"baidu.com/"];

[googlesendNext:@"google.com/"];

}

上面代碼輸出結(jié)果如下:

4.信號(hào)量的合并

信號(hào)量的合并說白了就是把兩個(gè)水管中的水合成一個(gè)水管中的水。但這個(gè)合并有個(gè)限制,當(dāng)兩個(gè)水管中都有水的時(shí)候才合并。如果一個(gè)水管中有水,另一個(gè)水

管中沒有水,那么有水的水管會(huì)等到無水的水管中來水了,在與這個(gè)水管中的水按規(guī)則進(jìn)行合并。下面這個(gè)實(shí)例就是把兩個(gè)信號(hào)量進(jìn)行合并。

(1) 首先創(chuàng)建兩個(gè)自定義的信號(hào)量letters和numbers

(2) 吧兩個(gè)信號(hào)量通過combineLatest函數(shù)進(jìn)行合并,combineLatest說明要合并信號(hào)量中最后發(fā)送的值

(3) reduce塊中是合并規(guī)則:把numbers中的值拼接到letters信號(hào)量中的值后邊。

(4) 經(jīng)過上面的步驟就是創(chuàng)建所需的相關(guān)信號(hào)量,也就是相當(dāng)于架好運(yùn)輸?shù)墓艿馈=又覀兙涂梢酝ㄟ^sendNext方法來往信號(hào)量中發(fā)送值了,也就是往管道中進(jìn)行灌水。

Objective-C

//組合信號(hào)

- (void)combiningLatest{

RACSubject *letters = [RACSubject subject];

RACSubject *numbers = [RACSubject subject];

[[RACSignal

combineLatest:@[letters, numbers]

reduce:^(NSString *letter, NSString *number){

return [letter stringByAppendingString:number];

}]

subscribeNext:^(NSString * x) {

NSLog(@"%@", x);

}];

//B1 C1 C2

[letters sendNext:@"A"];

[letters sendNext:@"B"];

[numbers sendNext:@"1"];

[letters sendNext:@"C"];

[numbers sendNext:@"2"];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21//組合信號(hào)

-(void)combiningLatest{

RACSubject*letters=[RACSubjectsubject];

RACSubject*numbers=[RACSubjectsubject];

[[RACSignal

combineLatest:@[letters,numbers]

reduce:^(NSString*letter,NSString*number){

return[letterstringByAppendingString:number];

}]

subscribeNext:^(NSString*x){

NSLog(@"%@",x);

}];

//B1 C1 C2

[letterssendNext:@"A"];

[letterssendNext:@"B"];

[numberssendNext:@"1"];

[letterssendNext:@"C"];

[numberssendNext:@"2"];

}

上面示例的運(yùn)行輸出結(jié)果如下:

下面是自己畫的原理圖,思路應(yīng)該還算是清晰。

5.信號(hào)的合并(merge)

信號(hào)合并就理解起來就比較簡單了,merge信號(hào)量規(guī)則比較簡單,就是把多個(gè)信號(hào)量,放入數(shù)組中通過merge函數(shù)來合并數(shù)組中的所有信號(hào)量為一個(gè)。類比一下,合并后,無論哪個(gè)水管中有水都會(huì)在merge產(chǎn)生的水管中流出來的。下方是merge信號(hào)量的代碼:

(1) 創(chuàng)建三個(gè)自定義信號(hào)量, 用于merge

(2) 合并上面創(chuàng)建的3個(gè)信號(hào)量

(3) 往信號(hào)里灌入數(shù)據(jù)

Objective-C

//合并信號(hào)

- (void)merge {

RACSubject *letters = [RACSubject subject];

RACSubject *numbers = [RACSubject subject];

RACSubject *chinese = [RACSubject subject];

[[RACSignal

merge:@[letters, numbers, chinese]]

subscribeNext:^(id x) {

NSLog(@"merge:%@", x);

}];

[letters sendNext:@"AAA"];

[numbers sendNext:@"666"];

[chinese sendNext:@"你好!"];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16//合并信號(hào)

-(void)merge{

RACSubject*letters=[RACSubjectsubject];

RACSubject*numbers=[RACSubjectsubject];

RACSubject*chinese=[RACSubjectsubject];

[[RACSignal

merge:@[letters,numbers,chinese]]

subscribeNext:^(idx){

NSLog(@"merge:%@",x);

}];

[letterssendNext:@"AAA"];

[numberssendNext:@"666"];

[chinesesendNext:@"你好!"];

}

上面代碼運(yùn)行結(jié)果如下:

上面示例的原理圖如下:

五. 在MVVM中引入RactiveCocoa

學(xué)以致用,最后來個(gè)簡單的實(shí)例,來感受一下如何在MVVM中使用RactiveCocoa。當(dāng)然今天RAC的應(yīng)用是非常簡單的,但原理就是這樣的。

接下啦我們要使用RAC模擬一下登錄功能,當(dāng)然,網(wǎng)絡(luò)請求也是模擬的,這不是重點(diǎn)。重點(diǎn)在于如何在MVVM各層之間使用RAC的信號(hào)量來更方便的在各個(gè)層

之間進(jìn)行響應(yīng)式數(shù)據(jù)交互。下面這個(gè)實(shí)例的UI是非常簡單的,并且實(shí)現(xiàn)起來也是灰常簡單的,關(guān)鍵還是在于RAC的應(yīng)用。

1.搭建Demo所需UI,用戶界面非常簡單,公有兩個(gè)用戶界面,一個(gè)是登錄頁面(兩個(gè)輸入框,一個(gè)登錄按鈕),一個(gè)是登錄后跳轉(zhuǎn)的頁面(一個(gè)展示

用戶名和密碼的Label)。下方是使用Storyboard實(shí)現(xiàn)的用戶登錄頁面。實(shí)現(xiàn)完后,個(gè)兩個(gè)頁面各自關(guān)聯(lián)一個(gè)ViewContorller類。

2.下方是整個(gè)小Demo的工程目錄,因?yàn)槲覀兘裉斓闹攸c(diǎn)是如何在MVVM中使用RAC,

所以重點(diǎn)在于RAC的應(yīng)用,對(duì)于MVVM的分層就簡化一些。下方有VC層,在VC層中有兩個(gè)視圖控制器,一個(gè)是登錄使用的視圖控制器

(ViewContorller)另一個(gè)是登錄成功后的視圖控制器(LoginSuccessViewController)。而ViewModel中則

是負(fù)責(zé)登錄的ViewModel業(yè)務(wù)邏輯層,該層中負(fù)責(zé)數(shù)據(jù)驗(yàn)證,網(wǎng)絡(luò)請求,數(shù)據(jù)存儲(chǔ)等一些與UI無關(guān)的業(yè)務(wù)邏輯。

3.實(shí)現(xiàn)登錄的ViewModel層

因?yàn)閂iewModel層是獨(dú)立于UI層而存在的,所以可以在沒有UI的情況下我們就可以去實(shí)現(xiàn)相應(yīng)模塊的ViewModel層。這正好減少了個(gè)個(gè)層次間的耦合性,同時(shí)也提高了可測試性,總體上改善了可維護(hù)性。好廢話少說,接下來要實(shí)現(xiàn)登錄的ViewModel層。

(1) 登錄ViewModel層對(duì)應(yīng)的類的頭文件中的內(nèi)容如下所示(VCViewModel.h),

其實(shí)下方一些常用的信號(hào)量可以抽象出來放到ViewModel的父類中,這為了簡化Demo沒有做父類的抽象。下方就是VCViewModel中

interface定義的公有屬性和公有方法(Public)。userName和password(NSString類型)

用來綁定用戶輸入的用戶名和密碼。下方三個(gè)自定義信號(hào)量successObject, failureObject, errorObject

用來發(fā)送網(wǎng)絡(luò)請求的數(shù)據(jù)。successObject負(fù)責(zé)處理網(wǎng)絡(luò)請求成功且符合正常業(yè)務(wù)邏輯的事件,

failureObject負(fù)責(zé)網(wǎng)絡(luò)請求成功不符合正常業(yè)務(wù)邏輯的處理,errorObject負(fù)責(zé)網(wǎng)絡(luò)異常處理。

Objective-C

//

//? VCViewModel.h

//? ReactiveCocoaDemo

//

//? Created by Mr.LuDashi on 15/10/19.

//? Copyright ? 2015年 ZeluLi. All rights reserved.

//

#import

@interface VCViewModel : NSObject

@property (nonatomic, strong) NSString *userName;

@property (nonatomic, strong) NSString *password;

@property (nonatomic, strong) RACSubject *successObject;

@property (nonatomic, strong) RACSubject *failureObject;

@property (nonatomic, strong) RACSubject *errorObject;

- (id) buttonIsValid;

- (void)login;

@end

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20//

//??VCViewModel.h

//??ReactiveCocoaDemo

//

//??Created by Mr.LuDashi on 15/10/19.

//??Copyright ? 2015年 ZeluLi. All rights reserved.

//

#import

@interfaceVCViewModel: NSObject

@property(nonatomic,strong)NSString*userName;

@property(nonatomic,strong)NSString*password;

@property(nonatomic,strong)RACSubject*successObject;

@property(nonatomic,strong)RACSubject*failureObject;

@property(nonatomic,strong)RACSubject*errorObject;

-(id)buttonIsValid;

-(void)login;

@end

上面可能說的有些抽象,結(jié)合項(xiàng)目中的實(shí)例來解釋一下什么時(shí)候發(fā)送successObject信號(hào)量,如何發(fā)送failureObject信號(hào)量,何時(shí)使用errorObject信號(hào)量。

以某些理財(cái)App中購買理財(cái)產(chǎn)品的業(yè)務(wù)流程為例。在用戶下單之前先去判斷用戶是否實(shí)名認(rèn)證以及綁定銀行卡,如果用戶已經(jīng)實(shí)名和綁定銀行卡就走正常支

付流程(用戶就是想去下單購買),VM就往VC發(fā)送successObject信號(hào),當(dāng)前VC就會(huì)根據(jù)信號(hào)量的指示跳轉(zhuǎn)到下單支付頁面。

但是如果用戶沒有實(shí)名或者綁卡,那么VM就給VC發(fā)送failureObject信號(hào),根據(jù)信號(hào)量中的參數(shù)來判斷是走實(shí)名認(rèn)證流程還是走綁定銀行卡流

程。

errorObject就比較簡單了,網(wǎng)絡(luò)異常,后臺(tái)服務(wù)器拋出的異常等不需要iOS這邊做業(yè)務(wù)邏輯處理的,就放在errorObject中負(fù)責(zé)錯(cuò)誤信息

的展示。

文字說完了,如果有些小伙伴還不太明白,那看下面這張?jiān)韴D吧。把三種信號(hào)量我們可以類比成十字路口的紅綠燈。successObject就是綠

燈,可以走正常流程。failureObject是黃燈,先等一下,完成該做的就可以走綠燈了。而errorObject就是一紅燈,報(bào)錯(cuò)異常,終止業(yè)務(wù)

流程并提升錯(cuò)誤信息。有圖有真相,到這兒如果還不理解我就沒招了。

在Public方法中- (id) buttonIsValid; 負(fù)責(zé)返回登錄按鈕是否可用的信號(hào)量。- (void)login;發(fā)起網(wǎng)絡(luò)請求,調(diào)用登錄網(wǎng)絡(luò)接口。

(2)代碼的具體實(shí)現(xiàn)如下(VCViewModel.m中的代碼),私有屬性如下。userNameSignal用來存儲(chǔ)用戶名的信號(hào)量,passwordSignal是用來存儲(chǔ)密碼的信號(hào)量。reqestData則是用來存儲(chǔ)返回?cái)?shù)據(jù)的。

Objective-C

@interface VCViewModel ()

@property (nonatomic, strong) RACSignal *userNameSignal;

@property (nonatomic, strong) RACSignal *passwordSignal;

@property (nonatomic, strong) NSArray *requestData;

@end

1

2

3

4

5@interfaceVCViewModel()

@property(nonatomic,strong)RACSignal*userNameSignal;

@property(nonatomic,strong)RACSignal*passwordSignal;

@property(nonatomic,strong)NSArray*requestData;

@end

(3)VCViewModel的初始化方法如下,負(fù)責(zé)初始化屬性。

Objective-C

- (instancetype)init

{

self = [super init];

if (self) {

[self initialize];

}

return self;

}

- (void)initialize {

_userNameSignal = RACObserve(self, userName);

_passwordSignal = RACObserve(self, password);

_successObject = [RACSubject subject];

_failureObject = [RACSubject subject];

_errorObject = [RACSubject subject];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16-(instancetype)init

{

self=[superinit];

if(self){

[selfinitialize];

}

returnself;

}

-(void)initialize{

_userNameSignal=RACObserve(self,userName);

_passwordSignal=RACObserve(self,password);

_successObject=[RACSubjectsubject];

_failureObject=[RACSubjectsubject];

_errorObject=[RACSubjectsubject];

}

(4) 發(fā)送登錄按鈕是否可用信號(hào)的方法如下,主要用到了信號(hào)量的合并。

Objective-C

//合并兩個(gè)輸入框信號(hào),并返回按鈕bool類型的值

- (id) buttonIsValid {

RACSignal *isValid = [RACSignal

combineLatest:@[_userNameSignal, _passwordSignal]

reduce:^id(NSString *userName, NSString *password){

return @(userName.length >= 3 & password.length >= 3);

}];

return isValid;

}

1

2

3

4

5

6

7

8

9

10

11//合并兩個(gè)輸入框信號(hào),并返回按鈕bool類型的值

-(id)buttonIsValid{

RACSignal*isValid=[RACSignal

combineLatest:@[_userNameSignal,_passwordSignal]

reduce:^id(NSString*userName,NSString*password){

return@(userName.length>=3&password.length>=3);

}];

returnisValid;

}

(5) 模擬網(wǎng)絡(luò)請求的發(fā)送,并發(fā)出網(wǎng)絡(luò)請求成功的信號(hào),具體代碼如下

Objective-C

- (void)login{

//網(wǎng)絡(luò)請求進(jìn)行登錄

_requestData = @[_userName, _password];

//成功發(fā)送成功的信號(hào)

[_successObject sendNext:_requestData];

//業(yè)務(wù)邏輯失敗和網(wǎng)絡(luò)請求失敗發(fā)送fail或者error信號(hào)并傳參

}

1

2

3

4

5

6

7

8

9

10

11-(void)login{

//網(wǎng)絡(luò)請求進(jìn)行登錄

_requestData=@[_userName,_password];

//成功發(fā)送成功的信號(hào)

[_successObjectsendNext:_requestData];

//業(yè)務(wù)邏輯失敗和網(wǎng)絡(luò)請求失敗發(fā)送fail或者error信號(hào)并傳參

}

4. 上面是VM的實(shí)現(xiàn),如果要進(jìn)行單元測試的話,就對(duì)相應(yīng)的VM類進(jìn)行初始化,調(diào)用相應(yīng)的函數(shù)進(jìn)行單元測試即可。接著就是看如何在相應(yīng)的VC模塊中使用VM。

(1) 在VC中實(shí)例化相應(yīng)的VM類,并綁定相應(yīng)的參數(shù)和實(shí)現(xiàn)接收不同信號(hào)的方法,具體代碼如下:

Objective-C

//關(guān)聯(lián)ViewModel

- (void)bindModel {

_viewModel = [[VCViewModel alloc] init];

RAC(self.viewModel, userName) = self.userNameTextField.rac_textSignal;

RAC(self.viewModel, password) = self.passwordTextField.rac_textSignal;

RAC(self.loginButton, enabled) = [_viewModel buttonIsValid];

@weakify(self);

//登錄成功要處理的方法

[self.viewModel.successObject subscribeNext:^(NSArray * x) {

@strongify(self);

LoginSuccessViewController *vc = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"LoginSuccessViewController"];

vc.userName = x[0];

vc.password = x[1];

[self presentViewController:vc animated:YES completion:^{

}];

}];

//fail

[self.viewModel.failureObject subscribeNext:^(id x) {

}];

//error

[self.viewModel.errorObject subscribeNext:^(id x) {

}];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32//關(guān)聯(lián)ViewModel

-(void)bindModel{

_viewModel=[[VCViewModelalloc]init];

RAC(self.viewModel,userName)=self.userNameTextField.rac_textSignal;

RAC(self.viewModel,password)=self.passwordTextField.rac_textSignal;

RAC(self.loginButton,enabled)=[_viewModelbuttonIsValid];

@weakify(self);

//登錄成功要處理的方法

[self.viewModel.successObjectsubscribeNext:^(NSArray*x){

@strongify(self);

LoginSuccessViewController*vc=[[UIStoryboardstoryboardWithName:@"Main"bundle:[NSBundlemainBundle]]instantiateViewControllerWithIdentifier:@"LoginSuccessViewController"];

vc.userName=x[0];

vc.password=x[1];

[selfpresentViewController:vcanimated:YEScompletion:^{

}];

}];

//fail

[self.viewModel.failureObjectsubscribeNext:^(idx){

}];

//error

[self.viewModel.errorObjectsubscribeNext:^(idx){

}];

}

(2) 點(diǎn)擊登錄按鈕,調(diào)用VM中登錄相應(yīng)的網(wǎng)絡(luò)請求方法即可

Objective-C

- (void)onClick {

//按鈕點(diǎn)擊事件

[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]

subscribeNext:^(id x) {

[_viewModel login];

}];

}

1

2

3

4

5

6

7-(void)onClick{

//按鈕點(diǎn)擊事件

[[self.loginButtonrac_signalForControlEvents:UIControlEventTouchUpInside]

subscribeNext:^(idx){

[_viewModellogin];

}];

}

到此為止,一個(gè)完整模擬登錄模塊的RAC下的MVVM就實(shí)現(xiàn)完畢。當(dāng)然上面的Demo是非常簡陋的,還有好多地方需要進(jìn)化。不過麻雀雖小,道理你懂得。主要是通過上面的Demo來感受一下RAC中的信號(hào)量機(jī)制以及應(yīng)用場景。

5.上面代碼寫完,我們就可以運(yùn)行看一下運(yùn)行效果了,下方是運(yùn)行后的效果,

上述工程GitHub分享鏈接:https://github.com/lizelu/MVVMWithReactiveCocoa

其他參考資料:

https://github.com/ReactiveCocoa/ReactiveViewModel

http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/

http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/

http://nshipster.cn/reactivecocoa/

http://limboy.me/ios/2013/06/19/frp-reactivecocoa.html

https://vimeo.com/65637501

http://southpeak.github.io/blog/2014/08/08/mvvmzhi-nan-yi-:flickrsou-suo-shi-li/

http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-%5B%3F%5D-:xin-hao/

http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-er-:twittersou-suo-shi-li/

ViewModel:

Kicking off network or database requests

Determining when information should be hidden or shown

Date and number formatting

Localization

ViewController:

Layout

Animations

Device rotation

View and window transitions

Presenting loaded UI

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容