最近看了一些MVVM的文章,总感觉说得很模糊,所以写出自己的看法。如果观点有出入,还望指正
MVVM架构图
先看文件结构划分
简单的交互页面展示
具体代码
- User中
这个model很简单,就是个数据结构model,
没有依赖项,完全解耦,可复用
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int height;
@end
- UserView中
UserView只暴露了3个方法
1.更新视图的方法。 2.与外界交互的代理方法。UserView没有依赖任何其他项,是完全解耦的,所以可以做到完全复用,其他地方想要使用只要调用这两个方法即可
.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol UserView_delegate <NSObject>
-(void) userViewAddHeight;
-(void) userViewJump;
@end
@interface UserView : UIView
//使用代理进行与ViewModel的交互
@property (nonatomic,weak) id<UserView_delegate> delegate;
//对外暴露更新视图的方法
- (void)updateViewWithName:(NSString *)name height:(int)height;
@end
NS_ASSUME_NONNULL_END
.m
#import "UserView.h"
@interface UserView()
@property (nonatomic, strong) UILabel *label_userInfo;
@property (nonatomic, strong) UIButton *button_addHeight;
@end
@implementation UserView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addSubview:self.label_userInfo];
[self addSubview:self.button_addHeight];
[self addSubview:self.button_jump];
self.backgroundColor = [UIColor systemPinkColor];
}
return self;
}
- (void)addHeight:(UIButton *)button {
if (self.delegate && [self.delegate respondsToSelector: @selector(userViewAddHeight)]) {
[self.delegate userViewAddHeight];
}
}
- (void)jump:(UIButton *)button {
if (self.delegate && [self.delegate respondsToSelector: @selector(userViewJump)]) {
[self.delegate userViewJump];
}
}
- (void)updateViewWithName:(NSString *)name height:(int)height {
self.label_userInfo.text = [NSString stringWithFormat:@"name:%@ height:%dcm",name,height];
}
-(UILabel *)label_userInfo {
if (!_label_userInfo) {
_label_userInfo = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 50)];
_label_userInfo.textColor = [UIColor blackColor];
_label_userInfo.backgroundColor = [UIColor whiteColor];
}
return _label_userInfo;;
}
- (UIButton *)button_addHeight {
if (!_button_addHeight) {
_button_addHeight = [[UIButton alloc] initWithFrame:CGRectMake(0, 120,150, 50)];
[_button_addHeight addTarget:self action:@selector(addHeight:) forControlEvents:(UIControlEventTouchUpInside)];
_button_addHeight.backgroundColor = [UIColor yellowColor];
[_button_addHeight setTitle:@"点击长高一公分" forState:(UIControlStateNormal)];
[_button_addHeight setTitleColor:[UIColor blackColor] forState:(UIControlStateNormal)];
}
return _button_addHeight;
}
- (UIButton *)button_jump {
if (!_button_jump) {
_button_jump = [[UIButton alloc] initWithFrame:CGRectMake(0, 120,150, 50)];
[_button_jump addTarget:self action:@selector(jump:) forControlEvents:(UIControlEventTouchUpInside)];
_button_jump.backgroundColor = [UIColor yellowColor];
[_button_jump setTitle:@"跳转到用户详情" forState:(UIControlStateNormal)];
[_button_jump setTitleColor:[UIColor blackColor] forState:(UIControlStateNormal)];
}
return _button_jump;
}
@end
- UserViewModel中
-
UserViewModel中
持有了UserView和处理User这个model的逻辑,减轻了原来ViewController的负担 -
通过kvo检测user的变化,实时更新视图 -
通过
遵循UserView的代理方法,进行用户交互、处理数据 -
对外暴露是
rUserView是readOnly修饰的,确保在外部不被修改 -
如果逻辑有什么变化,比如点击一次增加2cm,将可以直接在UserViewMode中处理,即代理方法找修改成
-(void)userViewAddHeight {
self.user.height = self.user.height + 2;
}
快速高效,且改动极小
.h
#import <Foundation/Foundation.h>
#import "UserView.h"
#import "User.h"
NS_ASSUME_NONNULL_BEGIN
@interface UerViewModel : NSObject<UserView_delegate>
@property (nonatomic, strong, readonly) UserView *rUserView;
- (void)requestUserData;
@end
NS_ASSUME_NONNULL_END
.m
#import "UerViewModel.h"
@interface UerViewModel()
@property (nonatomic, strong) User *user;
@property (nonatomic, strong) UserView *userView;
@end
@implementation UerViewModel
//处理网络请求
- (void)requestUserData {
//处理model转化
User *user = [User new];
user.usedId = @"9527";
user.height = 190;
user.name = @"lee";
//kvo会自动检测到变化,调用视图更新
self.user = user;
}
- (instancetype)init {
if (self = [super init]) {
self.user = [User new];
self.userView = [[UserView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 200)];
self.userView.delegate = self; //记得添加代理
//使用kvo检测model变化
[self addObserver:self forKeyPath:@"user" options:(NSKeyValueObservingOptionNew) context:nil];
[self addObserver:self forKeyPath:@"user.height" options:(NSKeyValueObservingOptionNew) context:nil];
};
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString: @"user"] || [keyPath isEqualToString: @"user.height"]) {
[self.userView updateViewWithName:self.user.name height:self.user.height];
}
}
//对外是readOnly
- (UserView *)rUserView {
return self.userView;
}
//代理方法
#pragma mark UserView_delegate
-(void)userViewAddHeight {
//KVO会自动监测更新
self.user.height++;
}
-(void)userViewJump {
//与ViewController交互,这里使用block
if (self.block) {
self.block(self.user.usedId);
}
}
-(void)dealloc {
//记得移除观察者
[self removeObserver:self forKeyPath:@"user"];
[self removeObserver:self forKeyPath:@"user.height"];
}
@end
- ViewController中
1.ViewController中代码极其简单,
仅仅是持有了viewModel而已,还有就是必要的主动请求数据的方法
2.ViewModel 与 ViewConroller的交互使用block,当然也可以使用代理等其他方式
3.如果这个ViewController中还有其他的View,只需要增加新的viewModel即可
4.由于ViewController仅持有ViewModel, 没有做其他数据处理,所以整个文件很轻,也很好维护
.m 文件
#import "RFViewViewController.h"
#import "UerViewModel.h"
@interface RFViewViewController ()
@property (nonatomic,strong) UerViewModel *userViewModel;
@end
@implementation RFViewViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userViewModel.rUserView];
[self.userViewModel requestUserData];
}
- (UerViewModel *)userViewModel {
if (!_userViewModel) {
_userViewModel = [[UerViewModel alloc] init];
//我这里是用block做UerViewModel 和 ViewViewController,当然也可以使用代理等其他方式
_userViewModel.block = ^(NSString * _Nonnull userId) {
使用MGJRouter 做路由跳转
[MGJRouter openURL:@"MVVM://UserDetail"withUserInfo:@{
@"navigationVC" : self.navigationController,
@"userid": userId },
}completion:nil];
};
}
return _userViewModel;
}
总结
- Model和View之间是完全解耦的,这两块可以完全复用
- ViewModel持有了model和view,包含了网络请求和逻辑处理,又通过KVO做到视图实时更新; 如果有网络或者逻辑等方面的修改,只需要在ViewModel修改即可,影响面很小
- ViewController持有ViewModel,但是由于ViewModel并不依赖ViewController,所以ViewModel是完全解耦了,如果其他地方需要用到这ViewModel模块,需要搬运ViewController中的代码即可,复用度极高。
- 真实项目上肯定不止一个ViewModel,按照这种模式,完全可以多设置几个ViewModel。各个模块分工明确,且交互并不杂糅在一起