iOS 项目中对 MVVM 实践
2018-12-10 本文已影响127人
人魔七七
前言
针对最近研究的一些知识点的实战,抽出一个模块对项目进行优化升级。
架构注意的两点
职责界限划分以及模块之间的通信
- 职责界限划分
- View 展示数据,并且会持有用户的点击事件,里面包含一些布局或者绘制的东西。
- Controller根据用户点击事件协调调用ViewModel里处理好的数据。观察ViewModel的变化并通知View更新。
- ViewModel 里面持有了UI有关的数据(这些数据是从服务器或者缓存获得经过处理的数据),以及获取数据的一些方法。
- 有时候为了避免ViewModel臃肿,把数据获取相关的逻辑(有可能是服务器上新的数据,也有可能是本地的数据)分离到一个新的模块来处理暂且叫Repository模块。
- Model 服务器返回的JSON对应的数据。
- 模块之间的通信
上层的组件持有下层组件的引用,下层组件不可以拥有上层组件的引用。
但是可以通过callback来处理,比如控制器观察ViewModel的方式通知控制器数据的变化,以便于控制器告诉View要update数据。
各模块的大致代码结构
Model
大致流程是从JSON映射到Model,Model属性可能需要我们自己重命名(可能服务器满足不了我们),Model最好能满足持久化需求,比如我要把某个Model存文件来缓存提供下一次使用。
ViewModel
-
这个类主要相应View传来的事件获取数据,(数据可能从服务器获取,也有可能从本地缓存获取,里面缓存策略,以及过期的处理还需要我们处理,通常还要和后台人员来商量)。
-
还有一些对应界面的data以及界面需要的必要的参数
-
还有我们数据处理中的一些状态,比如成功,失败等,这里是专门弄了一个类来处理。
NS_ASSUME_NONNULL_BEGIN
@interface WCSDurableGoodsRecipientsListViewModel : NSObject
#pragma mark - In 请求服务器或者本地缓存需要的参数
@property (copy, nonatomic) NSString *accountId;
@property (copy, nonatomic) NSString *searchTime;
@property (assign, nonatomic) NSInteger start;
#pragma mark - Out 界面上展示的数据
@property (copy, nonatomic) NSString *totalAppleyeStr;
@property (copy, nonatomic) NSString *awaitStr;
@property (strong, nonatomic) NSMutableArray *tableViewDataArray;
#pragma mark - NetWorkStatus 网络的状态
@property (strong, nonatomic) NetWorkResults *netWorkResults;
#pragma mark - Out 界面用户的事件
- (void)refresh;
- (void)loadMore;
@end
一个处理数据反馈状态的类
typedef NS_ENUM(NSInteger, NetWorkResultStatus) {
NetWorkResultStatusSuccess = 0,
NetWorkResultStatusError = 1,
NetWorkResultStatusLoading = 2,
};
@interface NetWorkResults : NSObject
@property (assign,readonly) NSInteger status;
@property (nonatomic,readonly) id content;
- (instancetype)initWithStatus:(NSInteger)status content:(id)content;
@end
Controller
这里主要是我们调用ViewModel的方法,以及监听ViewModel的界面数据的变化来更新我们的View界面。这里是观察数据代码
#pragma mark - KVO
- (void)wcs_handleViewModelUpdate
{
[self.KVOController observe:self.durableGoodsRecipientsListViewModel keyPath:FBKVOKeyPath(self.durableGoodsRecipientsListViewModel.tableViewDataArray) options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
id newValue = change[NSKeyValueChangeNewKey];
self.dataSourceArray = [(NSMutableArray *)newValue copy];
[self.tableView reloadData];
}];
[self.KVOController observe:self.durableGoodsRecipientsListViewModel keyPath:FBKVOKeyPath(self.durableGoodsRecipientsListViewModel.netWorkResults) options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
id newValue = change[NSKeyValueChangeNewKey];
if ([newValue isKindOfClass:[NetWorkResults class]])
{
NetWorkResults *result = (NetWorkResults *)newValue;
if (result.status == NetWorkResultStatusError)
{
[self showHUDWithTitle:@"" subTitle:result.content];
}
}
}];
}
注意:
- 我们用的FB的一个KVOController框架来观察ViewModel的数据变化以便update UI。这里标明RAC这个框架并不是MVVM必须的东西,其实它也是基于KVO封装的。
- 为了防止我们把keyPath写错可以用一个宏来处理如下。
#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))
对于一些特殊的子View处理比如Cell
可以针对cell创建一个ViewModel,在TableViewModel中处理下。
- (NSArray *)viewModelsDurableGoodsRecipients:(WCSDurableGoodsRecipientsListModelV2 *)applyrecords
{
NSMutableArray *viewModels = [NSMutableArray arrayWithCapacity:applyrecords.body.applyRecords.count];
for (WCSDurableGoodsRecipientsListApplyrecordsModelV2 *applyrecordsModelV2 in applyrecords.body.applyRecords) {
WCSDurableGoodsRecipientsListCellV2ViewModel *goodsRecipientsListCellV2ViewModel = [[WCSDurableGoodsRecipientsListCellV2ViewModel alloc] initWithGoods:applyrecordsModelV2];
[viewModels addObject:goodsRecipientsListCellV2ViewModel];
}
return viewModels.copy;
}
cell中的处理
- (void)bindViewModel:(WCSDurableGoodsRecipientsListCellV2ViewModel *)viewModel
{
// NSString *textStr = viewModel.applyrecordsModelV2.applyDesc;
}
控制器里的处理
- (void)configureCell:(WCSDurableGoodsRecipientsListCellV2 *)cell withObject:(WCSDurableGoodsRecipientsListCellV2ViewModel *)object
{
[cell bindViewModel:object];
}