在iOS项目中引入MVVM

简介:

本文翻译自: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 > 0 {
            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 > 0{
            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: 0)
        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: 0))
        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的,读者可以参考。本文代码在这里

 

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | Go:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!
相关文章
|
前端开发 iOS开发 设计模式
[译] iOS 里的 MVVM 和 RxSwift
在本文中,我将介绍 iOS 编程中的 MVVM 设计模式以及 RxSwift。本文分为两部分,第一部分简要介绍了设计模式和 RxSwift 的基础知识,而在 第二部分 里,有一个实现了 MVVM 和 RxSwift 的示例项目。
1282 0
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
102 1
|
1月前
|
设计模式 安全 Swift
探索iOS开发:打造你的第一个天气应用
【9月更文挑战第36天】在这篇文章中,我们将一起踏上iOS开发的旅程,从零开始构建一个简单的天气应用。文章将通过通俗易懂的语言,引导你理解iOS开发的基本概念,掌握Swift语言的核心语法,并逐步实现一个具有实际功能的天气应用。我们将遵循“学中做,做中学”的原则,让理论知识和实践操作紧密结合,确保学习过程既高效又有趣。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你打开一扇通往iOS开发世界的大门。
|
1月前
|
搜索推荐 IDE API
打造个性化天气应用:iOS开发之旅
【9月更文挑战第35天】在这篇文章中,我们将一起踏上iOS开发的旅程,通过创建一个个性化的天气应用来探索Swift编程语言的魅力和iOS平台的强大功能。无论你是编程新手还是希望扩展你的技能集,这个项目都将为你提供实战经验,帮助你理解从构思到实现一个应用的全过程。让我们开始吧,构建你自己的天气应用,探索更多可能!
62 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
2月前
|
开发框架 数据可视化 Java
iOS开发-SwiftUI简介
iOS开发-SwiftUI简介
|
4天前
|
设计模式 前端开发 Swift
探索iOS开发:从初级到高级的旅程
【10月更文挑战第31天】在这篇文章中,我们将一起踏上iOS开发的旅程。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。我们将从基础开始,逐步深入到更高级的技术和概念。让我们一起探索iOS开发的世界吧!
|
7天前
|
设计模式 前端开发 Swift
探索iOS开发:从初级到高级的旅程
【10月更文挑战第28天】在这篇技术性文章中,我们将一起踏上一段探索iOS开发的旅程。无论你是刚入门的新手,还是希望提升技能的开发者,这篇文章都将为你提供宝贵的指导和灵感。我们将从基础概念开始,逐步深入到高级主题,如设计模式、性能优化等。通过阅读这篇文章,你将获得一个清晰的学习路径,帮助你在iOS开发领域不断成长。
32 2
|
13天前
|
安全 API Swift
探索iOS开发中的Swift语言之美
【10月更文挑战第23天】在数字时代的浪潮中,iOS开发如同一艘航船,而Swift语言则是推动这艘船前进的风帆。本文将带你领略Swift的独特魅力,从语法到设计哲学,再到实际应用案例,我们将一步步深入这个现代编程语言的世界。你将发现,Swift不仅仅是一种编程语言,它是苹果生态系统中的一个创新工具,它让iOS开发变得更加高效、安全和有趣。让我们一起启航,探索Swift的奥秘,感受编程的乐趣。
|
15天前
|
Swift iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】在苹果生态系统中,SwiftUI的引入无疑为iOS应用开发带来了革命性的变化。本文将通过深入浅出的方式,带领读者了解SwiftUI的基本概念、核心优势以及如何在实际项目中运用这一框架。我们将从一个简单的例子开始,逐步深入到更复杂的应用场景,让初学者能够快速上手,同时也为有经验的开发者提供一些深度使用的技巧和策略。
41 1