关于MVVM的一些认识
在IOS开发之中,初学者总是被教导遵循MVC开发模式,后来又出现了MVVM模式,这些都是什么鬼?虽然如此,但很多人最开始写代码还是将所有的代码揉和在一个viewcontroller里面。既然揉和在一起和分模式写代码都能达到程序的实现,那么为什么要搞这些呢?
在实际的项目开发中,我们会发现如果全部将代码写在viewcontroller里面,那么一个VC出现三四千行代码都是很正常的,对于之后的维护与修改都是一个很麻烦的过程,楼主曾经接手过一个项目,之前的人就是将所有代码写在一个VC里面,一个VC里面的代码平均有3千多行,多的达到了5千行,当时我很想将项目去重构一次,这个时候你就能够明白用开发模式的重要性了。
话说回来,什么是MVC呢?这个网上文章一大堆,我就简单的说下,Attention:
4972583-c15629e86ba4c566.png网上找了一张很简单易懂的图片,如图所示,MVC就是将代码分为model(模型),view(视图),controller(控制器)三个模块,model和view不能直接沟通,需要通过controller进行对接。
view具体呈现的数据取决于model,用户修改model的数据,view上面的内容就会随之而改变,controller负责初始化model,并将model的数据传递给view去解析展示。
4972583-45e294e4486b9044.jpeg装修公司装修房子,项目主管告诉设计师装修的风格,设计师设计之后交给项目主管审核,然后项目主管再将设计图纸交给装修工人,在这个过程中设计师不会直接和装修工人进行交流,项目主管就相当于controller,设计师就是model,装修工人就是view。
在IOS开发之中,MVC模式主要的目的就是分离分离视图(View)和模型(Model),使用MVC模式我们达到了分离的效果,可是我们在开发当中会发现一个问题,那就是Controller(控制器)的体积越来越大。所以在IOS中的MVC也被称作Massive View Controller。
下面我们来看一段代码:
Model:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *userId;
@end
ViewController:
#import "MainViewController.h"
#import "User.h"
@interface MainViewController ()
@property (nonatomic, strong) UITableView *myTableView;
@property (nonatomic, strong) User *user;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.initialModel();
}
- (void)initialModel {
self.user = [[User alloc] init];
self.user.name = "lee"
self.user.userId = "123456"
}
@end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MainCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.nameLabel.text = self.user.name;
cell.userIdLabel.text = self.user.userId;
return cell;
}
同时,我们在开发的过程中又会发现一个问题,model里面又太轻量了,这就形成了头重脚轻,最大的一个问题来自于单元测试,由于Controller混合了业务逻辑和视图处理逻辑,所以在单元测试过程中分离这些成为了巨大的一个问题,当然,我们可以选择忽略它,那就是不进行单元测试。。。。
那么怎么办呢,于是就有了MVVM,我们需要把Controller进行优化、瘦身。
顾名思义,就是在MVC的基础上将Controller继续进行拆分,拆出来一个ViewModel,让我们将视图 UI 和业务逻辑分开。在MVVM架构中,View与ViewController均不能直接引用Model,而是通过引用ViewModel来间接引用Model。
简单来说,就是API请求完数据,解析成Model,之后在ViewModel中转化成能够直接被视图层使用的数据,交付给前端(View层)。
我们现在来看看MVVM的优点
-
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
-
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
-
独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成代码。
-
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
然后我们来看一看MVVM的框架图
timg.jpeg
对比上面MVC的框架图我们可以发现MVC中Controller需要做太多得事情,表示逻辑、业务逻辑,所以代码量非常的大。而MVVM则拆分了Controller里面的很多业务分给ViewModel
然后我们来看看MVVM下的代码:
Model:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *userId;
@end
ViewModel:
#import <Foundation/Foundation.h>
#import "User.h"
@interface UserViewModel : NSObject
@property (nonatomic, strong) User *user;
- (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId;
@end
#import "UserViewModel.h"
@implementation UserViewModel
- (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId {
self = [super init];
if (!self) return nil;
_user = [[User alloc] init];
if (userName.length > 0) {
_user.name = userName;
}else {
_user.name = @"未知";
}
_user.userId = userId;
return self;
}
@end
Controller:
#import "MainViewController.h"
#import "UserViewModel.h"
@interface MainViewController ()
@property (nonatomic, strong) UITableView *myTableView;
@property (nonatomic, strong) UserViewModel *userViewModel;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.initialViewModel();
}
- (void)initialViewModel {
_userViewModel = [[UserViewModel alloc] initWithUserName:@"lee" userId:123456];
}
@end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MainCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.nameLabel.text = self.userViewModel.name;
cell.userIdLabel.text = self.userViewModel.userId;
return cell;
}
根据代码我们可以看见,我们把原本在Controller里面应该处理的逻辑业务分离了出来,这样就完成了对Controller里面代码的瘦身,总体上减少了代码的复杂性。
但是我们也发现了一些问题。对于很简单的界面使用MVVM会增加代码量,但如果界面中内容很多、Cell样式也很多的情况下使用MVVM可以很好地将VC中处理Cell相关的工作分离出来。
所以,我们现在来看看MVVM的缺点:
- 数据绑定使得Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
- 数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
- 转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
- 只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
- 同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方
- 调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
还有一些具体的问题可以参考Casa Taloyum大神的博客《iOS应用架构谈 网络层设计方案》