iOS手把手教你实现一个规范的MVVM
2019-10-24 本文已影响0人
王方帅
Model:
1,先上代码:
DDDUSBaoIntroduceModel.h
@interface DDDUSBaoIntroduceModel : NSObject
@property (nonatomic,copy) NSString *top_info;
@property (nonatomic,copy) NSString *top_txt;
@property (nonatomic,copy) NSString *introduction;
@property (nonatomic,copy) NSString *introduction_info;
@property (nonatomic,copy) NSString *rate_txt;
@property (nonatomic,copy) NSArray *rate_info;
@property (nonatomic,copy) NSString *rule_url;
@property (nonatomic,copy) NSString *footer_txt;
@property (nonatomic,copy) NSString *footer_introduction;
@end
@interface DDDUSBaoKeyValueModel : NSObject
@property (nonatomic,copy) NSString *title;
@property (nonatomic,copy) NSString *value;
@end
DDDUSBaoIntroduceModel.m
@implementation DDDUSBaoIntroduceModel
+ (NSDictionary *)modelContainerPropertyGenericClass {
// value should be Class or Class name.
return @{@"rate_info" : [DDDUSBaoKeyValueModel class]};
}
@end
@implementation DDDUSBaoKeyValueModel
@synthesize value = _value;
- (NSString *)value {
return StrValid(_value)?_value:@"";
}
@end
2,可包含的内容
- .h中仅包含所有属性的声明
- .m中包含YYModel中需要的方法实现
- 如modelCustomPropertyMapper为自定义属性名对应关系的Mapper
- 如modelContainerPropertyGenericClass为数组、字典、集合中对应的模型类
- 如modelPropertyBlacklist和modelPropertyWhitelist分别为不给解析和只给解析的黑白名单
- 如modelCustomTransformFromDictionary和modelCustomTransformToDictionary中包含时间戳和NSDate的转换
3,不可包含的内容
- 注意Model不能在modelCustomTransformFromDictionary方法中增加太多转换代码,model应该是一个瘦model,原MVC中胖model的转换代码在MVVM中是viewModel的职责
View:
1,先上代码
.h中
@interface USBaoKLineView : UIView
@property (nonatomic,copy) NSString *title;
@property (nonatomic,strong) DDDUSBaoBuyViewModel *buyViewModel;
@end
.m中
@interface USBaoKLineView ()<IChartAxisValueFormatter,ChartViewDelegate>
@property (nonatomic, strong) LineChartView *chartView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic,copy) NSArray <DDDUSBaoKeyValueModel *>*rate_info;
@end
@implementation USBaoKLineView
//子View的创建
- (instancetype)init
{
self = [super init];
if (self) {
self.backgroundColor = DDDLightBlackColor;
[self createChartView];
_titleLabel = [UILabel sr_labelWithFrame:CGRectZero title:self.title font:BoldFont_DDD(15) textAlignment:NSTextAlignmentCenter titleColor:DDDWhiteColor];
[self addSubview:_titleLabel];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(10);
make.left.mas_equalTo(100);
make.right.mas_equalTo(-100);
make.height.mas_equalTo(30);
}];
}
return self;
}
//传入ViewModel的接口
- (void)setBuyViewModel:(DDDUSBaoBuyViewModel *)buyViewModel {
if (_buyViewModel != buyViewModel) {
_buyViewModel = buyViewModel;
_rate_info = buyViewModel.rate_info;
[self updateChartData];
}
}
@end
1,包含的内容
- 包含子View的创建
- 包含一个传入ViewModel的接口,内部从ViewModel中取出Model对View中所有子View进行数据填充。
ViewModel:
1,先上代码
@interface DDDUSBaoBuyViewModel : NSObject
@property (nonatomic,strong) DDDUSBaoIntroduceModel *model;
@property (nonatomic,copy) NSArray <DDDUSBaoKeyValueModel *>*rate_info;
- (void)fundDetailWithSuccess:(DDDRequestModelSucccessBlock)success
failure:(DDDRequestModelFailureBlock)failure;
@end
@implementation DDDUSBaoBuyViewModel
- (void)fundDetailWithSuccess:(DDDRequestModelSucccessBlock)success
failure:(DDDRequestModelFailureBlock)failure {
NSString *URLString = [NSString stringWithFormat:@"%@%@",
kRadarbrokersURL,
@"/appapi/fund/funddetail"];
NSDictionary *parameters = @{};
[DDDRequestManager POST:URLString parameters:parameters success:^(NSDictionary *responseDict) {
if ([responseDict[@"code"] integerValue] == 0) {
DDDUSBaoIntroduceModel *introduceModel = [DDDUSBaoIntroduceModel yy_modelWithJSON:responseDict[@"data"]];
self.model = introduceModel;
self.rate_info = introduceModel.rate_info;
if (success) {
success(self);
}
} else {
NSError *error = [NSError errorWithDomain:responseDict[@"msg"] code:[responseDict[@"code"] integerValue] userInfo:nil];
if (error) {
failure(error);
}
}
} failure:^(NSError *error) {
if (error) {
failure(error);
}
}];
}
@end
2,包含的内容
- 各View中用到的Model属性声明
- 网络请求发起及回调中模型转换及各Model属性赋值及成功失败回调代码
- 数据相关的从本地读取缓存及将网络返回数据缓存到本地
2,注意
一个VC中可包含多个Model,多个ViewModel,多个自定义View,VC负责某个View和ViewModel的绑定,View从ViewModel中取出对应的数据填充View
ViewController:
1,先上代码
//View的创建,布局
- (void)createUI {
[self.view addSubview:self.mainScrollView];
self.mainScrollView.backgroundColor = Radar_Page_Bg_Color;
WeakSelf(weakSelf)
[self.mainScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.mas_equalTo(0);
make.width.mas_equalTo(SCREEN_WIDTH);
make.height.equalTo(weakSelf.view.mas_height).offset(-44-32-kIPhone_Bottom_SafeArea_Height);
}];
UIView *headerView = [[UIView alloc] init];
[self.mainScrollView addSubview:headerView];
CGFloat imageHeight = SCREEN_WIDTH*(135/375.0);
[headerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.mas_equalTo(0);
make.width.mas_equalTo(SCREEN_WIDTH);
make.height.mas_equalTo(imageHeight+44);
}];
[headerView addSubview:self.annualizedLabel];
[self.annualizedLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(200);
make.centerX.mas_equalTo(0);
make.height.mas_equalTo(36);
make.centerY.equalTo(headerImageView.mas_centerY).offset(-15);
}];
}
#pragma mark -GetMethods
- (UIScrollView *)mainScrollView {
if (!_mainScrollView) {
_mainScrollView = [[UIScrollView alloc] init];
}
return _mainScrollView;
}
- (UILabel *)annualizedLabel {
if (!_annualizedLabel) {//2.27%
_annualizedLabel = [UILabel sr_labelWithFrame:CGRectZero title:@"" font:BoldFont_DDD(36) textAlignment:(NSTextAlignmentCenter) titleColor:[UIColor whiteColor]];
}
return _annualizedLabel;
}
- (DDDUSBaoBuyViewModel *)buyViewModel {
if (!_buyViewModel) {
_buyViewModel = [[DDDUSBaoBuyViewModel alloc] init];
}
return _buyViewModel;
}
//ViewModel的创建
WeakSelf(weakSelf);
[RadarHUD showLoading];
[self.buyViewModel fundDetailWithSuccess:^(id obj) {
[RadarHUD dismiss];
[weakSelf reloadData];
} failure:^(NSError *error) {
[RadarHUD dismiss];
[RadarToast showToastWithMessage:error.domain CompletionBlock:^{
[weakSelf goback:nil];
}];
}];
//数据绑定
- (void)reloadData {
DDDUSBaoIntroduceModel *model = _buyViewModel.model;
_annualizedLabel.text = model.top_info;
_annualizedPromptLabel.text = model.top_txt;
_barLabel.text = model.introduction;
[_introduceLabel sr_setText:model.introduction_info lineSpacing:10 withLimitWidth:SCREEN_WIDTH-17*2];
_klineView.rate_info = model.rate_info;
_klineView.title = model.rate_txt;
[_flexibleAccessLabel sr_setText:model.footer_txt lineSpacing:10 withLimitWidth:SCREEN_WIDTH-17*2];
[_tAdd0SurplusLabel sr_setText:model.footer_introduction lineSpacing:0 withLimitWidth:SCREEN_WIDTH-17*2];
}
2,包含的内容
- View的创建,布局(包括系统View和自定义View)
- ViewModel的创建
- ViewModel获取数据返回Model后将Model与View进行数据绑定(包括tableView的dataSource和delegate方法是属于数据绑定的范畴)
- 数据绑定时原则上View要跟ViewModel绑定,ViewModel中包含对应Model,而不是VC中包含对应Model
小结
根据Casa博客中说的总结了以下关键内容:
- ViewModel做什么事情?就是把JSON变成直接能被View使用的对象的一种Model。
- 在MVC的基础上,把C拆出一个ViewModel专门负责数据处理的事情,就是MVVM。
- 重剑无锋,大巧不工。在具体做View层架构的设计时,不需要拘泥于MVC、MVVM、VIPER等规矩。心法是大巧,要按照心法办事
- 拆分心法:天下功夫出少林,天下架构出MVC,拆分方式的不同诞生了各种不同的衍生架构方案,再怎么拆,只是招式,重点是达到Controller减负的目的。保证拆出来的模块可复用性高、封装度高(即需要最小的参数实现功能)。
参考文献及Demo:
https://github.com/ibireme/YYModel
https://github.com/coderyi/MVVMDemo
https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html