zoukankan      html  css  js  c++  java
  • iOS——MVVM设计模式

    一、典型的iOS构架——MVC

     

      在典型的MVC设置中,Model呈现数据,Vie呈现用户界面,而ViewController调节它两者之间的交互。

      虽然View和View Controller是技术上不同的组件,但他们总是手牵手在一起,成对的,View不能和不同的View Controller配对,反之亦然。View和View Controller之间的关系如下图所示:

      在典型的MVC应用里,许多逻辑被放在ViewController里。它们中的一些确实属于View Controller,但更多的是“表示逻辑”(presentation logic),就是那些将Model数据转换为View可以呈现的东西的事情,例如将一个NSData转换为一个格式化过的NSString。典型的MVC设计模式没有做太多的事情来解决iOS应用中日益增长的重量级视图控制器的问题。

    二、MVC的增强版——MVVM  

      在MVC中增加一个View Model,把所有表示逻辑放进去,它位于Model和View Controller之间,如下图所示:

      在MVVM中,正式链接了视图(View)和控制器(View Controller),并将表示逻辑从ViewController移出放到一个新的对象里,即View Model。

      MVVM的优势:

        *能减少ViewController的复杂性,并使得表示逻辑更易于测试;

        *可以兼容MVC架构;

        *增加应用程序的可测试性;

        *配合一个绑定机制效果最好。

      MVVM基本上就是MVC的改进版,所以很容易整合到现有使用典型MVC架构的应用中。

      下面看一个简单的Person Model以及相应的View Controller:

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @property (nonatomic,readonly) NSString *salutation;
    @property (nonatomic,readonly) NSString *firstname;
    @property (nonatomic,readonly) NSString *lastname;
    @property (nonatomic,readonly) NSDate *birthDate;
    
    -(instancetype)initWithSalutation:(NSString *)salutation firstname:(NSString *)firstname lastname:(NSString *)lastname birthDate:(NSDate *)birthDate;
    @end

    下面有一个PersonViewController,在ViewDidLoad里,只需要基于他的model属性设置一些Label即可:

    -(void)viewDidLoad{
        [super viewDidLoad];
        if (self.moedl.salutation.length > 0) {
            self.nameLabel.text = [NSString stringWithFormat:@"%@.%@.%@",self.moedl.salutation,self.moedl.firstname,self.moedl.lastname];
        }
        else{
            self.nameLabel.text = [NSString stringWithFormat:@"%@.%@",self.moedl.firstname,self.moedl.lastname];
        }
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
        [dateFormatter setDateFormat:@"EEEE MMMM d,yyyy"];
        self.birthdateLabel.text = [dateFormatter stringFromDate:self.moedl.birthDate];
    }

      

      这全都直接了当了,典型的MVC。现在来看看如何用一个ViewModel来增强它,创建一个PersonViewModel:

    PersonViewModel.h

    #import <Foundation/Foundation.h>
    #import "Person.h"
    
    @interface PersonViewModel : NSObject
    @property (nonatomic,readonly) Person *person;
    @property (nonatomic,readonly) NSString *nameText;
    @property (nonatomic,readonly) NSString *birthdateText;
    -(instancetype)initWithPerson:(Person *)person;
    @end

    PersonViewModel.m

    -(instancetype)initWithPerson:(Person *)person{
        self = [super init];
        if (!self) {
            return nil;
        }
        _person = person;
        if (person.salutation.length > 0) {
            _nameText = [NSString stringWithFormat:@"%@.%@.%@",self.person.salutation,self.person.firstname,self.person.lastname];
        }
        else{
            _nameText = [NSString stringWithFormat:@"%@.%@",self.person.firstname,self.person.lastname];
        }
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
        [dateFormatter setDateFormat:@"EEEE MMMM d,yyyy"];
        _birthdateText = [dateFormatter stringFromDate:person.birthDate];
        
        return self;
    }

      这样就将viewDidLoad中的表示逻辑加入viewModel里。此时,新的viewDidLoad就会非常轻量:

    -(void)viewDidLoad{
      [super viewDidLoad];
      self.nameLabel.text = self.viewModel.nameText;
      self.bithdateLabel.text = self.viewModel.birthdatetext;          
    }

      因此,我们并没有对MVC架构做太多改变。还是同样的代码,只是移动了位置。它与MVC兼容,带来更轻量级的ViewControllers。

      在MVVM里,我们试着尽可能多的将代码移入View Model里。测试ViewController就变得容易多了,因为它们不再做一大堆事情,并且View Model也非常易于测试。现在看下面的代码:

    SpecBegin(Person)
        NSString *salutation = @"Dr.";
        NSString *firstName = @"first";
        NSString *lastName = @"last";
        NSDate *birthdate = [NSDate dateWithTimeIntervalSince1970:0];
    
        it (@"should use the salutation available. ", ^{
            Person *person = [[Person alloc] initWithSalutation:salutation firstName:firstName lastName:lastName birthdate:birthdate];
            PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person];
            expect(viewModel.nameText).to.equal(@"Dr. first last");
        });
    
        it (@"should not use an unavailable salutation. ", ^{
            Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];
            PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person];
            expect(viewModel.nameText).to.equal(@"first last");
        });
    
        it (@"should use the correct date format. ", ^{
            Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];
            PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person];
            expect(viewModel.birthdateText).to.equal(@"Thursday January 1, 1970");
        });
    SpecEnd

      如果没有将这个逻辑移入View Model,我们将不得不实例化一个完整的View Controller以及伴随着的View,然后去比较View中Label值。这样做不只是会变成一个麻烦的间接层,而且它只代表了一个十分脆弱的测试。现在,可以按自己的意愿自由地修改视图层级而不必担心破坏单元测试。使用MVVM对测试带来的好处是显而易见的。

      注意到这个简单的例子中,Model是不可变的,所以可以只在初始化的时候指定View Model的属性。对于可变Model,还需要使用一些绑定机制,这样View Model就能在背后的Model改变时更新自身的属性。此为,一旦View Model上的Model发生改变,那View的属性也需要更新。Model的改变应该级联向下通过View Model进入View。

      在OS X上,可以使用cocoa绑定,但在iOS上并没有这样好的配置可用。但是,KVO为在iOS上实现提供了可能。

  • 相关阅读:
    SpringBoot-源码解析
    SpringBoot快速部署
    vue.js
    数据库设计--三范式
    mysql 建表及练习
    索引复制迁移
    nginx检查、重启、、、
    设计模式--Strategy 策略模式
    设计模式--设计原则
    设计模式--Template Method(模板方法)
  • 原文地址:https://www.cnblogs.com/mupiaomiao/p/4645150.html
Copyright © 2011-2022 走看看