ps:最近學(xué)習(xí)了ReactiveCocoa(RAC)脚线,就用這個(gè)結(jié)合MVVM的思想弄了個(gè)小項(xiàng)目屎媳,項(xiàng)目源碼已經(jīng)上傳到GitHub上,有興趣的同胞可以下載下來(lái)阔逼,源碼地址兆衅,下面我就抽出一個(gè)界面來(lái)介紹一下如何使用RAC+MVVM,例子是經(jīng)典的tableView類型。
先附上一張結(jié)構(gòu)圖
從圖中可以清晰地看到項(xiàng)目結(jié)構(gòu):MVVM 各自對(duì)應(yīng)的位置,具體的MVVM原理羡亩,網(wǎng)上資料一大堆摩疑,這里就不做贅述,我簡(jiǎn)要說(shuō)一下夕春,各自的部分都做了哪些功能:
M:這個(gè)不用說(shuō)未荒,是model層,我這里處理比較簡(jiǎn)單及志,只是單純的用來(lái)處理數(shù)據(jù)轉(zhuǎn)模型
V:view,主要用于數(shù)據(jù)展現(xiàn)
VM:這里是MVVM出現(xiàn)的重點(diǎn)所在片排,它主要用來(lái)處理數(shù)據(jù)分析和一些業(yè)務(wù)邏輯處理,我這里是將網(wǎng)絡(luò)請(qǐng)求以及告訴view層展現(xiàn)數(shù)據(jù)的業(yè)務(wù)都放在了這里(這里我在做的時(shí)候也有疑問(wèn)速侈,告訴view的動(dòng)作究竟是由controller做好率寡,還是放到vm中好,最后看了一些資料倚搬,覺(jué)得放在vm中更加理想冶共,前提是在控制器中就建立好vm和view的聯(lián)系,也就是將view綁定到vm中)每界,下面的例子可以看到捅僵。
最后就說(shuō)一下Controller了,在MVC中Controller是用來(lái)溝通M和V的眨层,它既要知道M何時(shí)發(fā)生了改變庙楚,又要隨時(shí)準(zhǔn)備告訴V去改變視圖展現(xiàn),相應(yīng)的一些業(yè)務(wù)邏輯處理也只能丟到C中趴樱,導(dǎo)致了C的臃腫馒闷,在MVVM中,可以極大地去減輕控制器的負(fù)擔(dān)叁征,從某種程度上纳账,比如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)分析以及一些業(yè)務(wù)邏輯都可以放到VM中捺疼。C這個(gè)時(shí)候也需要充當(dāng)中間人的角色疏虫,只不過(guò)它不用再去監(jiān)控M層變化,也不需要告訴View層改變數(shù)據(jù)展示啤呼,這些都可以由VM來(lái)代勞议薪。我看過(guò)一篇文章,說(shuō)的是控制器只需要處理必須放到控制器的邏輯媳友,例如頁(yè)面跳轉(zhuǎn)斯议,view的初始化,VM的初始化等醇锚,我深以為然哼御,在這個(gè)例子中我也是這樣處理的坯临。
下面,先開始從控制器層說(shuō)起:
1. 控制器: WLHomeController
實(shí)現(xiàn)功能
: 首頁(yè)的內(nèi)容是類似于新聞首頁(yè)恋昼,既有頂部標(biāo)簽(我這里處理的較簡(jiǎn)單看靠,頂部沒(méi)有滾動(dòng)選擇功能),內(nèi)容視圖又可以左右滾動(dòng)查看,同時(shí)上下可以聯(lián)動(dòng)液肌,又能保證再次回到出現(xiàn)過(guò)得界面不會(huì)再次自動(dòng)發(fā)送網(wǎng)絡(luò)請(qǐng)求挟炬。
-(WLTopTagView *)topTagView{
if (!_topTagView){
_topTagView = [[WLTopTagView alloc] initWithFrame:CGRectMake(0, kNavigationBarH, self.view.frame.size.width, 30)];
[self.view addSubview:_topTagView];
}
return _topTagView;
}
-(UIScrollView *)mainScrollView{
if (!_mainScrollView){
_mainScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.topTagView.frame) + 1,kScreenW, kScreenH -CGRectGetMaxY(self.topTagView.frame) - 1)];
_mainScrollView.backgroundColor = [UIColor whiteColor];
_mainScrollView.pagingEnabled = YES;
_mainScrollView.delegate = self;
[self.view addSubview:_mainScrollView];
}
return _mainScrollView;
}
- (NSMutableArray *)listTableViewArray {
if (!_listTableViewArray) {
_listTableViewArray = [NSMutableArray array];
}
return _listTableViewArray;
}
-(WLHomtTopViewModel *)topViewModel{
if (!_topViewModel){
_topViewModel = [[WLHomtTopViewModel alloc] init];
}
return _topViewModel;
}
-(NSMutableArray *)viewModelArray{
if (!_viewModelArray){
_viewModelArray = [NSMutableArray array];
}
return _viewModelArray;
}
這里是懶加載初始化必要視圖及數(shù)據(jù).
設(shè)計(jì)方案
: 我采用的是UIScrollView
+ UITableView
的結(jié)構(gòu),有興趣的同胞可以嘗試一下UICollectionView
+ UITableView
的結(jié)構(gòu)來(lái)實(shí)現(xiàn)
我這里沒(méi)有采用復(fù)用幾個(gè)tableView的思想嗦哆,這里是可以優(yōu)化的點(diǎn)谤祖,可以復(fù)用兩個(gè)或者三個(gè)tableView去節(jié)約內(nèi)存。
將view和viewModel綁定
:
[viewModel bindViewToViewModel:tableView];
主要是通過(guò)viewModel中提供的接口
- (void)bindViewToViewModel:(UIView *)view {
self.tableView = (UITableView *)view;
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerNib:[UINib nibWithNibName:@"WLHomeListCell" bundle:nil] forCellReuseIdentifier:@"listCell"];
}
題外話
:
- (void)bindViewToViewModel:(UIView *)view
再好一點(diǎn)的做法是創(chuàng)建一個(gè)VM基類老速,把這個(gè)方法抽出來(lái)粥喜,所有的VM都繼承于這個(gè)基類,分別取實(shí)現(xiàn)這個(gè)方法橘券。我比較懶额湘,一開始沒(méi)考慮到這種情況,后來(lái)就不想改了旁舰,湊合著看吧
到這里你可能會(huì)有疑問(wèn)锋华,那么如何去使用RAC呢?下面我就來(lái)簡(jiǎn)要說(shuō)一下如何去用箭窜,如何建立起控制器和VM之間的關(guān)系:
2.如何運(yùn)用RAC+VM
@property (nonatomic,strong,readonly) RACCommand *homeListCommand;
連接VM和C的東西就是它 毯焕,關(guān)于RAC的原理和實(shí)現(xiàn)我也講不出來(lái),只會(huì)用绽快,如果你想了解芥丧,可以去搜索一些RAC的資料紧阔,自行學(xué)習(xí)撒~~~
在VM中需要這樣做:
//獲取首頁(yè)列表數(shù)據(jù)
- (void)requestHomeListInfo {
@weakify(self);
_homeListCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
@strongify(self);
if (!self.firstLoadData) {
return [RACSignal empty];
}
self.firstLoadData = NO;
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[WLNetworkTool sharedInstance] loadHomeListData:[input intValue] success:^(id response) {
[subscriber sendNext:response];
[subscriber sendCompleted];
} failure:^(NSError *error) {
[subscriber sendError:error];
[subscriber sendCompleted];
}];
return nil;
}];
return [requestSignal map:^id(NSDictionary *value) {
NSArray *data = value[@"data"][@"items"];
NSArray *modelsArray = [[data.rac_sequence map:^id(id value) {
return [WLHomeListModel mj_objectWithKeyValues:value];
}] array];
//這一步刷新列表可以放到這里坊罢,也可以放到控制器里面
NSLog(@"請(qǐng)求首頁(yè)列表數(shù)據(jù)成功 %@",modelsArray);
if (modelsArray && modelsArray.count > 0) {
[self.homeListArray removeAllObjects];
[self.homeListArray addObjectsFromArray:modelsArray];
[self.tableView reloadData];
}
return modelsArray;
}];
}];
}
RACCommand內(nèi)部是擁有一個(gè)signal的,我們所謂的網(wǎng)絡(luò)請(qǐng)求也就是在這個(gè)里面去弄擅耽,至于何時(shí)觸發(fā)活孩,下面再介紹,現(xiàn)在先來(lái)把這一段給簡(jiǎn)要說(shuō)一下:
內(nèi)部信號(hào)requestSignal
里面實(shí)現(xiàn)的東西需要用[requestSignal map:^id(NSDictionary *value)
觸發(fā)乖仇,你打斷點(diǎn)可以看到憾儒,先執(zhí)行的是[requestSignal map:^id(NSDictionary *value)
,而后信號(hào)激活變成熱信號(hào),才會(huì)去執(zhí)行信號(hào)里面的內(nèi)容乃沙,也就是網(wǎng)絡(luò)請(qǐng)求起趾。
網(wǎng)絡(luò)請(qǐng)求成功后,會(huì)執(zhí)行map里面的內(nèi)容警儒,我這里為了熟悉RAC,特意用了map去對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換训裆,想省事眶根,可以在這里直接用MJExtension就行,最后才會(huì)走到控制器中訂閱的block之中
[requestSignal subscribeNext:^(NSArray *x) {
// self.currentTableView = self.listTableViewArray[index];
NSLog(@"請(qǐng)求首頁(yè)列表數(shù)據(jù)成功 %@",x);
// if (x && x.count > 0) {
// [viewModel.homeListArray removeAllObjects];
// [viewModel.homeListArray addObjectsFromArray:x];
// [self.currentTableView reloadData];
// }
}];
那么這個(gè)command又是什么時(shí)候執(zhí)行的呢边琉,這就需要控制器去觸發(fā)執(zhí)行時(shí)間了属百,其實(shí)很簡(jiǎn)單,就一句話
[viewModel.homeListCommand execute:@(model.ID)];
這就是整個(gè)觸發(fā)流程变姨,簡(jiǎn)單說(shuō)可以理解為以下幾個(gè)步驟:
1.在vm中定義command
2.在控制器中將view和vm綁定
3.在控制器中觸發(fā)command執(zhí)行時(shí)機(jī)
4.在vm中進(jìn)行網(wǎng)絡(luò)請(qǐng)求并處理數(shù)據(jù)族扰,通知view刷新數(shù)據(jù)
這個(gè)是控制器和vm之間的通信,通過(guò)信號(hào)機(jī)制解決定欧,那么vm和v之間是如何實(shí)現(xiàn)使用RAC的呢渔呵?這就需要RAC中的訂閱者,顧名思義忧额,訂閱之就是先訂閱對(duì)象厘肮,然后在合適的時(shí)機(jī)觸發(fā)執(zhí)行條件,那么訂閱的內(nèi)容就會(huì)執(zhí)行睦番,這個(gè)比較類似于OC中的block类茂,其實(shí)就是block,先保存要執(zhí)行的block塊托嚣,然后在某個(gè)時(shí)間點(diǎn)觸發(fā)執(zhí)行block操作巩检,訂閱者的實(shí)現(xiàn)也是類似。
3.VM和V之間通信
首先你需要在V中有一個(gè)訂閱者 @property (nonatomic,strong) RACSubject *cellSubject;
這個(gè)訂閱者是被V擁有的示启,觸發(fā)時(shí)機(jī)是由外界觸發(fā)兢哭,所以,在V中需要這樣寫:
self.cellSubject = [RACSubject subject];
@weakify(self);
[self.cellSubject subscribeNext:^(WLHomeListModel *model) {
@strongify(self);
// NSLog(@"傳送過(guò)來(lái)模型了");
self.descLabel.text = model.title;
self.likeControl.text = [NSString stringWithFormat:@"%d",model.likes_count];
self.likeControl.image = model.liked ? [UIImage imageNamed:@"content-details_like_selected_16x16_"] : [UIImage imageNamed:@"Feed_FavoriteIcon_17x17_"];
[self.coverImageView sd_setImageWithURL:[NSURL URLWithString:model.cover_image_url] placeholderImage:[UIImage imageNamed:@"PlaceHolderImage_small_31x26_"]];
self.model = model;
}];
而在VM中需要這樣觸發(fā):
WLHomeListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"listCell" forIndexPath:indexPath];
[cell.cellSubject sendNext:self.homeListArray[indexPath.row]];
這是不是很像block的使用邏輯呢夫嗓?當(dāng)然原理是不一樣的迟螺,為了方便理解,可以這樣認(rèn)為~~~
那么舍咖,有的需求是v中點(diǎn)擊某個(gè)按鈕矩父,需要告訴控制器或者別的類去做相應(yīng)的操作,這個(gè)時(shí)候怎么辦呢排霉?也需要訂閱者參與:
不過(guò)不同的是窍株,訂閱者的初始化操作不在V中,而是在需要被通知的那個(gè)類中攻柠,這個(gè)例子中就是VM球订,V是負(fù)責(zé)激活訂閱者的,那么我可不可以這樣理解:誰(shuí)是需要被告知執(zhí)行某個(gè)任務(wù)的對(duì)象瑰钮,它就要?jiǎng)?chuàng)建訂閱者冒滩,誰(shuí)需要激活訂閱者,誰(shuí)就要執(zhí)行send操作浪谴?這只是我個(gè)人的理解开睡,有不對(duì)的地方可以指出祈搜,能讓我更加理解RAC+MVVM的操作>-<
V中有一個(gè)喜歡按鈕,點(diǎn)擊喜歡士八,需要作出相應(yīng)的處理:
在V中:
@property (nonatomic,strong) RACSubject *likeSubject;
點(diǎn)擊喜歡按鈕后,激活訂閱者:
[[self.likeControl rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
@strongify(self);
[self.likeSubject sendNext:@(self.model.ID)];
[self.likeSubject sendCompleted];
}];
VM中創(chuàng)建訂閱者容燕,并保存訂閱者需要執(zhí)行的操作:
[cell.likeSubject subscribeNext:^(id x) {
NSLog(@"點(diǎn)擊了喜歡 %d",[x intValue]);
}];
這樣也就完成了VM和V之間的通訊(正向反向都有)
這只是我個(gè)人看了一些RAC資料后練習(xí)的小項(xiàng)目,里面肯定有很多問(wèn)題婚度,如果你有疑問(wèn)或者對(duì)RAC+MVVM有別的理解的蘸秘,很高興你能為我指正,感激不盡蝗茁,最后再附上源代碼地址