一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
基础概念
M(Model):数据层,负责网络数据的处理、数据持久化存储和读取等工作
-瘦Model:只有数据展示相关
-胖Model:数据展示+数据相关逻辑处理
V(View):负责数据渲染,用户交互
C(Controller):负责连接View层和Model层,响应view事件和作为view的代理,以及界面跳转和生命周期的处理等任务
问题1:viewController属于什么?
既属于View又属于Controller,所以在MVC架构中viewController会很臃肿。
问题2:使用YYModel时,json转model中的model是什么?
属于瘦Model,只有数据展示相关,无数据处理逻辑
MVC
错误的使用方式,在cell里设置一个属性model,重写model的setter方法,在里面进行赋值或者处理相应逻辑。代码如下:
@interface JXCurrentMVCCell : UITableViewCell
@property (nonatomic, strong) JXCurrentMVCModel *model;
@end
- (void)setModel:(JXCurrentMVCModel *)model {
_model = model;
self.label.text = self.model.name;
}
这种写法在逻辑上没什么错误,但是在架构上来说是错误的,因为它让Model层和View层直接交互。不符合MVC的架构思想。我们看下面这张图:
这是非常经典的一张图,说明了MVC架构中,Model、Controller、View之间的关系。Model只能跟Controller交互,View只能跟Controller交互,Model和View之间是不能直接交互的,只能通过Controller进行交互。通知、代理、KVO、target-action等都是回调的的一种方式,可以自由选择合适的方式。
我们可以看下面的代码,model负责处理数据,通过kvo通知controller使用,tableView是我们的View层,通常情况下会自定义一个cell,controller把model的数据赋值给cell,如果cell有点击事件可以通过代理的方式回调到controller来处理,controller再告知model应该怎样去处理点击事件的数据。
@interface JXCurrectMVCController ()<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) JXCorrectMVCModel *model;
@end
@implementation JXCurrectMVCController
- (void)dealloc {
[self.model removeObserver:self forKeyPath:@"responseObject"];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
self.model = [[JXCorrectMVCModel alloc]init];
[self addObserver:self.model forKeyPath:@"responseObject" options:NSKeyValueObservingOptionNew context:nil];
[self.model getData];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"responseObject"]) {
[self.tableView reloadData];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
cell.textLabel.text = **self**.model.responseObject;
return cell;
}
MVP
通过上述描述我们可以知道,因为MVC模式的viewController既有view的属性,也有controller的属性,所以会导致大量的代码都在这里实现,导致controller非常臃肿。我们可以使用MVP的架构方式,presenter来分担controller的功能,基本思想如下:
在这个模式下,view和model的职责没有什么改变,他们也是不能直接交互的,现在由presenter来处理view和model。可以预见presenter由于业务的复杂也会导致代码量越来越多,好像跟MVC的controller臃肿的问题一样的,但是我们可以拆分业务,不同的业务绑定不同的view和presenter的方式来解耦,我们来看下代码如下:
controller
- (void)viewDidLoad {
[super viewDidLoad];
JXMVPView *view = [[JXMVPView alloc]initWithFrame:self.view.bounds];
[self.view addSubview:view];
JXMVPModel *model = [[JXMVPModel alloc]init];
self.presenter = [[JXMVPPresenter alloc]initWithView:view model:model];
[model getData];
}
presenter
@interface JXMVPPresenter ()
@property (nonatomic, strong) JXMVPView *view;
@property (nonatomic, strong) JXMVPModel *model;
@end
@implementation JXMVPPresenter
- (instancetype)initWithView:(JXMVPView *)view model:(JXMVPModel *)model {
self = [super init];
if (self) {
self.view = view;
self.model = model;
[self.model getData];
[self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
self.view.lblName.text = self.model.name;
}
}
- (void)reloadData {
self.view.lblName.text = self.model.name;
self.view.avatarImageView.image = [UIImage imageNamed:self.model.avatar];
}
@end
MVVM
MVVM的思想跟MVP差不多,他是用viewModel来处理绑定的逻辑,基本一提到MVVM就会说MVVM必须要跟RAC一起使用,MVVM是一种架构思想,RAC只是让绑定这种方式更加方便,如果不使用RAC我们也是可以实现MVVM的,代码如下:
controller
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor yellowColor];
/// view
self.lblName = [[UILabel alloc]initWithFrame:CGRectMake(0, 100, 100, 20)];
[self.view addSubview:self.lblName];
self.nameButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 140, 140, 20)];
[self.nameButton setTitle:@"namechange" forState:UIControlStateNormal];
[self.nameButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.view addSubview:**self**.nameButton];
/// viewModel
__weak typeof(self) ws = self;
self.viewModel = [[JXMVVMViewModel alloc]init];
/// bind
self.viewModel.dataDidChange = ^(NSString * _Nonnull name) {
ws.lblName.text = name;
};
[self.nameButton addTarget:self.viewModel action: @selector(nameChanged) forControlEvents:UIControlEventTouchUpInside];
}
viewModel
- (instancetype)init {
self = [super init];
if (self) {
self.model = [[JXMVVMModel alloc]init];
[self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[self.model getData];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (self.dataDidChange) {
self.dataDidChange(self.model.name);
}
}
- (void)nameChanged {
self.model.name = @"zhangsan";
}
上面我们说了这么多架构方式,但是我们需要的是什么?
架构的本质其实是为了解决三个问题,解耦、可测、易用。不管我们使用MVC,还是MVP,还是MVVM或者其他的架构方式,只要能解决这三个问题,就是好的架构方式。我们在一个简单的业务模式下使用MVC,因为这种架构方式简单易懂,因为业务模式简单,代码量比较少。在复杂的业务模式下使用MVP
模式,因为MVVM需要建立太多的绑定关系,而我们的工程并没有使用RAC,而RAC的学习曲线比较陡峭,目前团队也没有这方面的学习规划。
demo地址: juexiao-time.oss-cn-shanghai.aliyuncs.com/iOS-%E5%AD%…
参考链接