本文翻译自: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 的这两点好处。
有三点一定在文中强调:
如前文所述,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 并取得了不错的效果。还有这里的一个,完全是基于 MVVM 的,读者可以参考。
来源: http://lib.csdn.net/article/ios/42872