- 面临开发的业务逻辑越来越错综复杂,MVC已经很大程度上不能完全满足我们的需求,于是就出现了MVVM、MVP等架构。其中,最广为人知的是MVVM,虽说上手没那么容易,但是出于它能为controller减压的优越性,被广泛使用。
- 重量级视图控制器会让整个ViewController变得非常复杂且不可维护,让维护者接近崩溃却无从下手。(题外话:MVP实际用起来确实没有MVVM用起来效果明显。)
MVP
MVP模式是MVC的一个演化版本,MVP全称Model-View-Presenter
- Model:主要提供数据的存储功能,一般都是用来封装网络获取的json数据的集合对象,Presenter通过调用Model进行对象交互。
- View:View与MVC中的V又有一些小差别,MVP中的View可以是Viewcontroller、View等控件,Presenter通过向View传model数据进行交互。
- Presenter:作为model和view的中间人,从model层获取数据之后传给view,使得View和model没有耦合,义务逻辑处理可以在此处处理
MVP模式目录结构
说明:对比MVC目录结构,多了Presenter的目录,数据交互处理都在Presenter中。就我理解,MVC中的Controller像一个大家族的大管家,MVP中的Presenter像这个家族中某个人的私人秘书
MVP模式代码及分析
Controller结构代码分析
@protocol HomeViewProtocol
///城市列表
- (void)onGetResponseCityListEntity:(NSArray*)homeEntity fail:(NSInteger) errorCode des:(NSString *)des;
///行情列表
- (void)onGetResponseCondtionListEntity:(NSArray*)condtionEntity fail:(NSInteger) errorCode des:(NSString *)des;
@end
@interface HomeViewController ()
@property (nonatomic,strong) HomePresenter *homePresenter;
@property (nonatomic,strong) HomeCondtionPresenter *homeOtherPresenter;
@property (nonatomic,strong) NSMutableArray *listArray;
@property (nonatomic,strong) UITableView *tableView;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
_listArray = [[NSMutableArray alloc]initWithArray:@[@[],@[]]];
_tableView = ({
UITableView *tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-64) style:UITableViewStyleGrouped];
[tableView registerNib:[UINib nibWithNibName:@"HomeViewCell" bundle:nil] forCellReuseIdentifier:NSStringFromClass([self class])];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
tableView;
});
//初始化HomePresenter,然后绑定一下自己的视图,
_homePresenter = [[HomePresenter alloc]initWithView:self];
[_homePresenter getCityResponseList];
_homeOtherPresenter = [[HomeCondtionPresenter alloc]initWithView:self];
[_homeOtherPresenter getCondtionResponseList];
}
///遵循HomeViewProtocol
- (void)onGetResponseCityListEntity:(NSArray *)homeEntity fail:(NSInteger)errorCode des:(NSString *)des {
if (homeEntity) {
_listArray[0] = [NSMutableArray arrayWithArray:homeEntity];
[_tableView reloadData];
}
}
- (void)onGetResponseCondtionListEntity:(NSArray *)condtionEntity fail:(NSInteger)errorCode des:(NSString *)des {
if (condtionEntity.count > 0) {
_listArray[1] = [NSMutableArray arrayWithArray:condtionEntity];
[_tableView reloadData];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return _listArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _listArray[section].count;
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
HomeViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([self class])];
[cell setModel:_listArray[0][indexPath.row]];
return cell;
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"UITableViewCell"];
}
NSDictionary *parm = _listArray[1][indexPath.row];
cell.textLabel.text = parm[@"StoreName"]?:@"";
cell.detailTextLabel.text = parm[@"StoreAddr"]?:@"";
return cell;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
return 44;
} else {
return 66;
}
}
@end
- 因为MVP是协议式编程模式,所有需要遵守HomeViewProtocol协议。实现了该协议,当然需要实现对应的协议方法(onGetResponseCityListEntity: fail: des)。
- 因为MVP是协议式编程模式,所有也需要绑定对应的View或者ViewController,所有[xxxPreenseter initWithView:self]方法。
Presenter结构代码及分析
@interface HomePresenter : HttpPresenter>
///测试城市列表请求
- (void)getCityResponseList;
@end
@implementation HomePresenter
///测试城市列表请求
- (void)getCityResponseList {
NSString *url = @"https://mobileapi.centanet.com/index/url";
[self.httpClient get:url parameters:nil];
}
- (void)onSuccess:(id)responseObject fail:(id)clientInfo errCode:(NSInteger)errCode errInfo:(NSString *)errInfo {
if ([_view respondsToSelector:@selector(onGetResponseCityListEntity:fail:des:)]) {
if (responseObject) {
HomeEntity * model = [HomeEntity yy_modelWithJSON:responseObject];
[_view onGetResponseCityListEntity:model.Result fail:errCode des:errInfo];
} else {
[_view onGetResponseCityListEntity:@[] fail:errCode des:errInfo];
}
}
}
@end
代码说明
- 因为HomePresenter是继承HttpPresenter的,所以当调用自身的get或者post请求的时候,需要实现(onSuccess: fail: errCode: errInfo)请求回调方法
- “HttpPresenter
>”此泛型写法,将需要实现的协议方法转移到HomePresenter绑定的V中,本例子中就是通过这样的方法,将处理好的数据回调到Controller中去了
MVVM
MVVM模式也是MVC的一个演化版本,全称Model-View-ViewModel
- Model:数据模型,和MVP,MVC没区别
- View:在和MVC 和 MVP 模式下,View就是用户界面
- ViewModel:是一个公开公共属性和命令的抽象的view。取代了 MVC 模式的 controller,或 MVP 模式的任命者(presenter),MVVM 有一个驱动。在 viewmodel 中,这种驱动传达视图和数据绑定的通信。
MVVM模式目录结构
MVVM模式代码及分析
Controller代码及分析
@interface TemplateViewController ()
@property (nonatomic,strong) UITableView *tableView;
@property (nonatomic,strong) TemplateViewModel *templateViewModel;
@end
@implementation TemplateViewController
- (void)viewDidLoad {
[super viewDidLoad];
_templateViewModel = [[TemplateViewModel alloc]init];
_tableView = ({
UITableView *tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-64) style:UITableViewStyleGrouped];
[tableView registerNib:[UINib nibWithNibName:@"TemplateCell" bundle:nil] forCellReuseIdentifier:NSStringFromClass([TemplateCell class])];
[tableView registerNib:[UINib nibWithNibName:@"StoreViewCell" bundle:nil] forCellReuseIdentifier:NSStringFromClass([StoreViewCell class])];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
tableView;
});
WS(weakSelf)
[_templateViewModel setReturnBlock:^(NSURLSessionDataTask *task, id responseObject) {
[weakSelf.tableView reloadData];
} failureBlock:^(NSURLSessionDataTask *task, NSError *error) {
}];
[self addHttpOperation:[_templateViewModel getCityList]];
[self addHttpOperation:[_templateViewModel getStoreList]];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [_templateViewModel tempNumberOfSection];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [_templateViewModel tempNumberOfSection:section];
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
TemplateCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([TemplateCell class])];
[cell setModel:[_templateViewModel tempIndexPathOfCity:indexPath.row]];
return cell;
} else {
StoreViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([StoreViewCell class])];
[cell setModel:[_templateViewModel tempIndexPathOfStore:indexPath.row]];
return cell;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
return 44;
} else {
return 66;
}
}
@end
代码说明
- _templateViewModel对象调用getCityList和getStoreList方法,获取服务器数据,并处理数据
- _templateViewModel对象通过block回调,刷新View的数据
- tableView刷新数据后,通过_templateViewModel获取对应的数据条数和每个cell显示的数据内容
ViewModel代码及分析(重点)
@interface TemplateViewModel : BaseViewModel
@property (nonatomic,strong) NSMutableArray *resultStoreList;
//获取城市列表数据
- (NSURLSessionDataTask*)getCityList;
//获取门店列表数据
- (NSURLSessionDataTask*)getStoreList;
//tableView的组数(默认设置了2个section)
- (NSInteger)tempNumberOfSection;
//tableView每组显示的条数
- (NSInteger)tempNumberOfRowInSection:(NSInteger)section;
//第一个section的cell对应的model
- (TemplateResultEntity*)tempIndexPathOfCity:(NSInteger)row;
//第二个section的cell对应的model
- (NSDictionary*)tempIndexPathOfStore:(NSInteger)row;
@end
@implementation TemplateViewModel
- (instancetype)init {
if (self = [super init]) {
_resultStoreList = [[NSMutableArray alloc]init];
}
return self;
}
//获取城市列表数据
- (NSURLSessionDataTask *)getCityList{
WS(weakSelf)
return [ServiceClientRequest requestForCityListParm:nil success:^(NSURLSessionDataTask *task, id responseObject) {
TemplateEntity *model =[TemplateEntity yy_modelWithJSON:responseObject];
[weakSelf.resultArray addObjectsFromArray:model.Result];
if (weakSelf.successBlock) {
weakSelf.successBlock(task,responseObject);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if (weakSelf.failureBlock) {
weakSelf.failureBlock(task,error);
}
}];
}
//获取门店列表数据
- (NSURLSessionDataTask *)getStoreList {
WS(weakSelf)
return [ServiceClientRequest requestForStoreParm:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *resultArray = responseObject[@"Result"];
[weakSelf.resultStoreList addObjectsFromArray:resultArray];
if (weakSelf.successBlock) {
weakSelf.successBlock(task,responseObject);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if (weakSelf.failureBlock) {
weakSelf.failureBlock(task,error);
}
}];
}
- (NSInteger)tempNumberOfSection {
return 2;
}
- (NSInteger)tempNumberOfRowInSection:(NSInteger)section{
if (section == 0) {
return self.resultArray.count;
} else {
return self.resultStoreList.count;
}
}
- (TemplateResultEntity*)tempIndexPathOfCity:(NSInteger)row {
return self.resultArray[row];
}
- (NSDictionary *)tempIndexPathOfStore:(NSInteger)row {
return self.resultStoreList[row];
}
@end
代码说明
- getCityList和getStoreList负责向远程服务器获取数据和映射成对应的数据结构模型
- tempNumberOfSection返回对应tableView的组数
- tempNumberOfRowInSection返回对应的tableView某个indexSection的row数组个数
当tableView触发刷新数据的时候,就会从这些方法中获取需要的数据,从而将数据处理和逻辑处理给转移到ViewModel上了