iOS架构-MVC-MVP-MVVM

275 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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的架构思想。我们看下面这张图: image.png

这是非常经典的一张图,说明了MVC架构中,ModelControllerView之间的关系。Model只能跟Controller交互,View只能跟Controller交互,ModelView之间是不能直接交互的,只能通过Controller进行交互。通知、代理、KVO、target-action等都是回调的的一种方式,可以自由选择合适的方式。

我们可以看下面的代码,model负责处理数据,通过kvo通知controller使用,tableView是我们的View层,通常情况下会自定义一个cellcontrollermodel的数据赋值给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的功能,基本思想如下:

image.png

在这个模式下,viewmodel的职责没有什么改变,他们也是不能直接交互的,现在由presenter来处理viewmodel。可以预见presenter由于业务的复杂也会导致代码量越来越多,好像跟MVCcontroller臃肿的问题一样的,但是我们可以拆分业务,不同的业务绑定不同的viewpresenter的方式来解耦,我们来看下代码如下:

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

image.png 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%…

参考链接

github.com/wujunyang/M…

juejin.cn/post/684490…

blog.devtang.com/2015/11/02/…