本文翻译自:http://www.objc.io/issue-13/mvvm.html。为了方便读者并节约时间,有些不是和文章主题相关的就去掉了。如果读者要看原文的话可以通过前面的url直接访问。作者也是做了iOS多年,从大学一直到现在n多年了。对于开发一款有B格的APP很有追求。学习了很多的东西,比如,silver bullet什么的,设计模式什么的。但是,面对急速膨胀的代码量,即使才高八斗也显得无所适从。于是他开始思考人生。。。

MVC?还有另外一个解释:Massive View Controller,翻译过来就是一大堆的View Controller的意思。有的时候真的时有这种感觉,View Controller太多了。尤其在一个人晚上加班改bug的时候,感觉更明显。于是,你会恨不得全部推倒重来算了!

从架构的角度考虑,也许MVC的一个衍生架构MVVM更加的合适。这里就不讨论MVVM的前世今生了。园子里的各位.NET达人从很久以前就已在WPF上玩这个东西了。先看一下iOS的MVC是什么样的,然后一步一步的进入MVVM。iOS的MVC:

这是一个典型的MVC架构。Models代表数据,views代表的时用户界面,view controllers在中间协调。仔细一想你会发现,虽然view和view controller是两个完全不同的东西,但是他们却紧密结合。什么时候一个view可以和不同的view controller结合使用,或者一个view controller可以和不同的view结合使用。所以,这个架构其实是这样的:

这样就跟准确的描述了MVC架构下的代码是怎么写的。但是,这还没有反应出来iOS开发中大量增加的view controller。在典型的MVC应用中,很多的逻辑处理放在view controller中。有些确实是需要放在view controller中的。但是还有很大一部分式“展示逻辑(presentation logic)”。这是一个MVVM的术语,指的是那些把Model里d的数据转换成view中可以显示的形式的过程。比如,把一个NSData转换成有一定格式的NSString就是这么一个过程。

上面的图都少了一部分内容。缺少了的就是那部分“展示逻辑”。这一部分抽象出来之后就可以叫做“view model”。这一部分在view、view controller和model之间。

看起来更合理了把。这附图准确的描述了什么是MVVM,一个增强版的MVC。这里,我们可以正式的把view和controller连接起来。并把展示逻辑从view controller中挪出去,形成一个新的对象:view model。MVVM听起来复杂,其实就是一个穿了件新衣的MVC。本质上和MVC是一样的。

现在您应该清楚什么是MVVM了。那么,为什么要用这个东西呢?因为,使用这个架构编写代码可以减少view controller的复杂度,并且展示的逻辑也更容易测试。下面就通过代码展示一下MVVM的这两点好处。

有三点一定在文中强调:

  1. MVVM和您现有的MVC架构是兼容的。
  2. MVVM使您的APP更容易测试。
  3. MVVM可以和绑定型的架构很好共存。

如前文所述,MVVM就是一个加强版的MVC。所以很容易看到这个模式如何和之前的MVC架构共存。我们先创建一个简单的Person model和对应的view controller。

//
// Person.swift
// MVVM
//
// Created by Bruce Lee on 9/12/14.
// Copyright (c) 2014 Dynamic Cell. All rights reserved.
// QQ群:58099570
// import UIKit class Person {
var salutation: String?
var firstName: String?
var lastName: String?
var birthDate: NSDate? init(salutation: String?, firstName: String?, lastName: String?, birthDate: NSDate?){
self.salutation = salutation
self.firstName = firstName
self.lastName = lastName
self.birthDate = birthDate
}
}

现在假设我们有一个view controller叫做PersonViewController,在viewDidLoad中要用model Person来设定一些Label的值:

    override func viewDidLoad() {
super.viewDidLoad() if self.model.salutation!.utf16Count > {
self.nameLabel.text = "\(self.model.salutation) \(self.model.firstName) \(self.model.lastName)"
}
else{
self.nameLabel.text = "\(self.model.firstName) \(self.model.lastName)"
} var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE MMMM d, yyyy"
self.birthDateLabel.text = dateFormatter.stringFromDate(self.model.birthDate!)
}

这些都是最常规的MVC代码的写法。下面看看如何写view model。

import UIKit

class PersonViewModel {
var person: Person!
var nameText: String?
var birthDateText: String? init(person: Person){
self.person = person if self.person?.salutation?.utf16Count > {
self.nameText = "\(self.person.salutation) \(self.person.firstName) \(self.person.lastName)"
}
else{
self.nameText = "\(self.person.firstName) \(self.person.lastName)"
} var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE MMMM d, yyyy"
self.birthDateText = dateFormatter.stringFromDate(self.person.birthDate!)
}
}

这样,显示逻辑已经从viewDidLoad方法中迁移了出去。现在viewDidLoad方法就显得轻量了很多。

    override func viewDidLoad() {
super.viewDidLoad() self.nameLabel.text = self.viewModel.nameText
self.birthDateLabel.text = self.viewModel.birthDateText
}

你会看到,并不需要对MVC架构做多大的修改。几乎只是原来的代码稍作移动。完全和MVC兼容,减少了view controller的重量,而且变得更加容易测试。

下面说说可测试。view controller的难以测试简直是出了名的。在MVVM中,代码很大一部分都移到了view model中。并且view model都是很容易测试的。如:

var person: Person!

    override func setUp() {
super.setUp()
var salutation = "Dr."
var firstName = "first"
var lastName = "last"
var birthDate = NSDate(timeIntervalSince1970: )
self.person = Person(salutation: salutation, firstName: firstName, lastName: lastName, birthDate: birthDate)
} func testUserSalutation(){
var viewModel = PersonViewModel(person: self.person)
XCTAssert(viewModel.nameText! == "Dr. first last" , "use salutation available \(viewModel.nameText!)")
} func testNoSalutation(){
var localPerson = Person(salutation: nil, firstName: "first", lastName: "last", birthDate: NSDate(timeIntervalSince1970: ))
var viewModel = PersonViewModel(person: localPerson)
XCTAssert(viewModel.nameText! == "first last", "should not use salutation \(viewModel.nameText!)")
} func testBirthDateFormat(){
var viewModel = PersonViewModel(person: self.person)
XCTAssert(viewModel.birthDateText! == "Thursday January 1, 1970", "date \(viewModel.birthDateText!)")
}

如果不是把代码移到了view model中,测试的时候就不得不初始化一个view controller,当然还有view controller里的一堆view。然后对比的时这些view(上例提到的时Label)的某些属性的值。这样不仅是非常的不方便、不直接,而且还会形成一种非常脆弱的测试:因为,view controller的试图层次根本就不能有任何的变动,一旦变动测试即告失败!或者测试根本就编译不过。使用MVVM对于测试的益处是显而易见的。在上例中还是比较简单的逻辑。如果换做其他的产品环境的复杂的显示逻辑的话,这种易于测试的好处会更加明显。

注意到,上面使用到的例子都是比较简单的。没什么需要修改的属性。可以直接使用初始值初始化对象。对于有修改的model。我们需要用到某中绑定机制。这样在model的某些值修改以后才能保证基于这个model的view model的值也跟着改变。更深一步,model值修改之后,view调用的view model的值也能跟着修改。也就是,一个对于model的修改,和model相关的view model和view的值都能同步的更新。

在OSX上,可以使用Cocoa bindings。但是iOS上没有这个奢侈品。但是key-value observation完全可以胜任。只是在很多属性的时候会增加代码量。也可以使用ReactiveCocoa。当然这只是一个选择。一个不错的绑定框架,会使MVVM好上加好。

我们已经讲了很多关于MVVM的内容。从易于测试的角度讲了MVVM。一个好的绑定框架会使MVVM更加好用。如果要更多的了解MVVM的话可以看这里。这些文章更多的讲述了MVVM的好处。或者这一篇,讲述了在原有项目上使用MVVM并取得了不错的效果。还有这里的一个开源app,完全是基于MVVM的,读者可以参考。本文代码在这里

在iOS项目中引入MVVM的更多相关文章

  1. ios项目中引用其他项目复习

    ios项目中引用其他开源项目,今天再次复习了,记个备注. 1. 将开源项目的.xcodeproj拖入项目frameworks 2. Build Phases下 Links Binary With Li ...

  2. iOS项目中常见的文件

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  3. iOS 10 中引入了 Message 框架

    WWDC 2016 上最重磅的消息之一就是在 iOS 10 中引入了 Message 框架.开发者现在可以为苹果内置的 Messages 应用开发扩展啦.通过开发一个应用扩展,你可以让用户跟应用在 M ...

  4. 在Android项目中引入MuPdf

    由于公司手机App要加入一个附件查看功能,需要查看PDF文件,在网上找了许多第三方工具,最后选择了MuPDF. 更多第三方工具可以查看大神总结的:http://www.cnblogs.com/poke ...

  5. iOS-C文件添加到iOS项目中,运行报错

    iOS-C文件添加到iOS项目中,运行报错 问题: 往项目中添加一个空的c文件, 编译运行; 出现2,30个编译错误. 原因: 由于在项目中添加了Pch文件,在文件中所有代码还没有开始运行之前, pc ...

  6. 如何在项目中引入 #include .h、.lib、 .dll、.cpp (转)

    源:http://blog.csdn.net/vippolka/article/details/8552735 在项目中引入.h..lib和dll.以及.cpp 1..h的引入 解决办法1:把  XX ...

  7. 如何在项目中引入MetaQ消息收发机制

    当需要异步发送和接收大量消息时,需要在Crystal项目中引入MetaQ消息收发机制. 关于MetaQ使用的官方例子可参考:https://github.com/killme2008/Metamorp ...

  8. eclipse项目中引入shiro-freemarker-tags会jar包冲突

    maven项目中引入了这个依赖. <dependency> <groupId>net.mingsoft</groupId> <artifactId>sh ...

  9. 项目中引入composer

    众所周知,composer可以自定义加载插件库和依赖,它也是用PHP写的,怎样在自己的项目中引入并使用composer呢?. 1.新建一个项目,在项目的根目录创建composer.json文件,用过一 ...

随机推荐

  1. 国内yum源的安装(163,阿里云,epel)

    ----阿里云镜像源 1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2.下载新的 ...

  2. Unreal Enginer4特性介绍

    转自:http://blog.csdn.net/cartzhang/article/details/39401991 一.特性说明 特性 Ue4是一款专业开发高质量游戏的平台开发工具.Ue4的渲染加快 ...

  3. Custom Exception in ASP.NET Web API 2 with Custom HttpResponse Message

    A benefit of using ASP.NET Web API is that it can be consumed by any client with the capability of m ...

  4. 磁盘IO计算

    看了篇文章,突然想写点磁盘IO的东西,也算是对磁盘的一点点总结. 以下以理论为主,辅助结合实际情况.不明白这句话的出去. 今年是2018年,目前市场上早已经没有国产的硬盘,以前的长城.易拓早早的被拍死 ...

  5. React性能优化 PureComponent

    为什么使用? React15.3中新加了一个 PureComponent 类,顾名思义, pure 是纯的意思, PureComponent 也就是纯组件,取代其前身 PureRenderMixin  ...

  6. Raspberry Pi 安装FTP(Pure-FTP)

    Raspbian版本: 安装: 因为使用的是默认的pi用户,所以下面的命令都带着sudo. 安装Pure-FTP sudo apt-get install pure-ftpd 2. 创建用户组ftpg ...

  7. JSON格式化工具推荐

    JSON以其独特的简洁方便及与Javscript的无缝集成在WEB2.0时瓦风靡全球.   不过做为开发者,当看到一段很长的未格式化的JSON代码时,你会不会感到头晕? {"meta&quo ...

  8. VCF文件导入导出

    参考资料 通讯录导入导出vcf格式文件方法可参考: https://qiaodahai.com/android-iphone-mobile-phones-contacts-import-and-exp ...

  9. hadoop学习day2开发笔记

    1.将hdfs客户端开发所需的jar导入工程(jar包可在hadoop安装包中找到common/hdfs) 2.写代码 要对hdfs中的文件进行操作,代码中首先需要获得一个hdfs的客户端对象 Con ...

  10. tensorflow-eagerAPI

    调用该API可以不通过 tensorflow.Session.run()调用 定义的张量constant tensor,可以直接print # -*- coding: utf-8 -*- from _ ...