前言
上一篇文章中,筆者簡(jiǎn)單的閱讀了ReactiveCocoa官方文檔穆趴,了解了ReactiveCocoa的基本使用后饿凛。這篇文章主要探討一下,如何基于MVVM的設(shè)計(jì)模式在含有UITableView界面中使用RAC綁定數(shù)據(jù)剖笙。
MVVM
MVVM本質(zhì)上是基于MVC的一個(gè)改進(jìn)版卵洗,它是在傳統(tǒng)MVC模式上添加了一個(gè)ViewModel。ViewModel可以取出 Model 的數(shù)據(jù)同時(shí)幫忙處理 View 中由于需要展示內(nèi)容而涉及的業(yè)務(wù)邏輯弥咪,為Controller減壓过蹂。
實(shí)戰(zhàn)
下面筆者將基于ReactiveCocoa+MVVM+UITableView,實(shí)現(xiàn)一個(gè)常見的列表展示聚至,以及按鈕點(diǎn)擊改變cell中的字體顏色榴啸。
源碼地址:
https://github.com/pengwj/blogWork/tree/master/code/ReacttiveObjC-MVVM-tableview
Controller
移除網(wǎng)絡(luò)請(qǐng)求以及數(shù)據(jù)處理后的Controller異常的簡(jiǎn)潔。我看很多人將tablevie的delegate晚岭、datasoure代理方法都放到了ViewModel中鸥印,但是考慮到這樣的話ViewModel就綁定了View勋功,所以最終決定將代理方法都放在VC中,具體可以下載我的代碼看看库说。
// 核心代碼如下
- (void)initViewModel {
_viewModel = [MainViewModel new];
@weakify(self)
[_viewModel.fetchProductCommand.executing subscribeNext:^(NSNumber *executing) {
NSLog(@"command executing:%@", executing);
if (!executing.boolValue) {
@strongify(self)
[self.tableView.mj_header endRefreshing];
}
}];
[_viewModel.fetchMoreProductCommand.executing subscribeNext:^(NSNumber *executing) {
if (!executing.boolValue) {
@strongify(self);
[self.tableView.mj_footer endRefreshing];
}
}];
[_viewModel.errors subscribeNext:^(NSError *error) {
NSLog(@"something error:%@", error.userInfo);
//TODO: 這里可以選擇一種合適的方式將錯(cuò)誤信息展示出來(lái)
}];
}
- (void)bindViewModel {
@weakify(self);
[RACObserve(self.viewModel, dataArray) subscribeNext:^(id x) {
@strongify(self);
NSLog(@"bindViewModel-dataArray");
[self.tableView reloadData];
}];
}
Model
model層相對(duì)MVC模式?jīng)]有改變狂鞋,只是簡(jiǎn)單的賦值操作。
//MainModel.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MainModel : NSObject
@property (nonatomic, copy) NSString *rtTitle;
@property (nonatomic, copy) NSString *rtSelect;
+ (instancetype)mainModelWithDic:(NSDictionary *)dic;
@end
NS_ASSUME_NONNULL_END
// MainModel.m
+ (instancetype)mainModelWithDic:(NSDictionary *)dic
{
MainModel *model = [[MainModel alloc] init];
model.rtTitle = dic[@"title"];
model.rtSelect = dic[@"select"];
return model;
}
View
View中主要做了數(shù)據(jù)綁定潜的,監(jiān)聽了Model以及一些控件響應(yīng)事件
// MainCell.m
- (void)bindData
{
@weakify(self)
[RACObserve(self, model.rtTitle) subscribeNext:^(id x) {
@strongify(self)
self.rtLabel.text = self.model.rtTitle;
}];
[RACObserve(self, model.rtSelect) subscribeNext:^(id x) {
if ([self.model.rtSelect isEqualToString:@"0"]) {
self.rtLabel.textColor = [UIColor yellowColor];
} else {
self.rtLabel.textColor = [UIColor redColor];
}
}];
self.rtButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input){
NSLog(@"按鈕被點(diǎn)擊");
if ([self.model.rtSelect isEqualToString:@"0"]) {
self.model.rtSelect = @"1";
} else {
self.model.rtSelect = @"0";
}
return [RACSignal empty];
}];
}
ViewModel
ViewModel中需要實(shí)例化網(wǎng)絡(luò)請(qǐng)求的RARACCommand骚揍,以及監(jiān)聽網(wǎng)絡(luò)請(qǐng)求回來(lái)的數(shù)據(jù),并對(duì)數(shù)據(jù)做相應(yīng)的邏輯處理啰挪。
- (void)initCommed
{
_dataArray = [NSMutableArray arrayWithCapacity:0];
@weakify(self)
_fetchProductCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
@strongify(self)
self.pageIndex = 0;
return [[[APIClient sharedClient]
fetchProductWithPageIndex:self.pageIndex]
takeUntil:self.cancelCommand.executionSignals];
}];
_fetchMoreProductCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
self.pageIndex = self.pageIndex+1;
return [[[APIClient sharedClient] fetchProductWithPageIndex:self.pageIndex] takeUntil:self.cancelCommand.executionSignals];
}];
}
- (void)initSubscribe {
@weakify(self);
[[_fetchProductCommand.executionSignals switchToLatest] subscribeNext:^(id responseObject) {
@strongify(self);
NSDictionary *responseDict = (NSDictionary *)responseObject;
NSInteger code = [[responseDict objectForKey:@"code"] integerValue];
if (code == 0) {
NSArray *infoArray = [responseDict objectForKey:@"info"];
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:0];
[infoArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull dic,NSUInteger idx,BOOL * _Nonnull stop){
MainModel *model = [MainModel mainModelWithDic:dic];
[tempArray addObject:model];
}];
/// ??????注意這里需要通過(guò)KVC的方式對(duì)數(shù)組進(jìn)行操作
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.dataArray.count, [tempArray count])];
[[self mutableArrayValueForKey:@"dataArray"] insertObjects:tempArray atIndexes:indexSet];
} else {
[self.errors sendNext:[NSError new]];
}
}];
[[_fetchMoreProductCommand.executionSignals switchToLatest] subscribeNext:^(id responseObject) {
@strongify(self);
NSDictionary *responseDict = (NSDictionary *)responseObject;
NSInteger code = [[responseDict objectForKey:@"code"] integerValue];
if (code == 0) {
NSArray *infoArray = [responseDict objectForKey:@"info"];
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:0];
[infoArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull dic,NSUInteger idx,BOOL * _Nonnull stop){
MainModel *model = [MainModel mainModelWithDic:dic];
[tempArray addObject:model];
}];
/// ??????注意這里需要通過(guò)KVC的方式對(duì)數(shù)組進(jìn)行操作
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.dataArray.count, [tempArray count])];
[[self mutableArrayValueForKey:@"dataArray"] insertObjects:tempArray atIndexes:indexSet];
} else {
[self.errors sendNext:[NSError new]];
}
}];
[[RACSignal merge:@[_fetchProductCommand.errors, self.fetchMoreProductCommand.errors]] subscribe:self.errors];
}
遇到的坑
直接獲取NSMutableArray進(jìn)行操作不會(huì)觸發(fā)RAC的監(jiān)聽信號(hào)信不,所以這里需要通過(guò)KVC來(lái)添加、移除數(shù)據(jù)亡呵。
// 通過(guò)KVC獲取數(shù)組
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
// eg
// 正確的例子
[[self mutableArrayValueForKey:@"dataArray"] addObjectsFromArray:tempArray];
// 錯(cuò)誤的例子
[self.dataArray addObjectsFromArray:tempArray];
總結(jié)
可能是由于這個(gè)是前幾年比較火的技術(shù)抽活,我在網(wǎng)上查找了很多資料都沒有一個(gè)比較完整、統(tǒng)一的實(shí)現(xiàn)方法锰什。主要集中如下:
- UITableView的代理方法下硕、數(shù)據(jù)源回調(diào)方法是否需要封裝到ViewModel中
- dataArray數(shù)據(jù)存儲(chǔ)數(shù)組是放在ViewModel中,還是放在VC中
- Cell的數(shù)據(jù)綁定理應(yīng)是應(yīng)用RAC監(jiān)聽Model的變化汁胆,但是部分文章的實(shí)現(xiàn)依然是主動(dòng)調(diào)用方法去刷新
最終筆者選擇了一個(gè)還比較符合心目中對(duì)MVVM理解的文章進(jìn)行學(xué)習(xí)梭姓,然后實(shí)現(xiàn)了這個(gè)demo。
ReactiveCocoa發(fā)展之際可能剛好遇到swift橫空出世嫩码,導(dǎo)致RAC在OC還沒有完全普及誉尖,大量開發(fā)者就轉(zhuǎn)向swift以及RxSwift了。目前網(wǎng)上搜索RAC的文章都是幾年前的铸题,而RxSwift則都是最新的文章释牺。這里在此立個(gè)flag,要開始學(xué)習(xí)swift啦回挽。
推薦閱讀:
文章
RACObserving an NSMutableArray
一次MVVM+ReactiveCocoa實(shí)踐
ReactiveCocoa+MVVM實(shí)戰(zhàn)
iOS如何為NSMutableArray添加KVO
源碼
MVVMReactiveCocoa
MVVMReactiveCocoaDemo
MVVMDemo
MVVMReactiveCocoa