我们可以通过以下方式来理解MVVM与MVC/MVCS/VIPER之间的异同:
MVC: View/VC + Model
MVCS: View/VC + Store + Model
MVVM: View/VC + ViewModel + Model
VIPER: View/VC + Wireframe/Presenter / Interactor/Data Manager + Entity(Model)
从上面的列表,我们可以非常清晰地看到,不管是MVCS、MVVM还是VIPER,其实重点主要在于对VC代码的拆分上。
MVC(Model-View-Controller
)框架是苹果针对Mac OS X / iOS开发推荐的编程范式。Model用于存储数据,view提供给用户一个交互界面,而view controller负责model与view之间的交互。
The view notifies the controller of any user interaction. The view controller then updates the model to reflect the change of state. That model then (typically through Key-Value-Observation
) notifies any controllers of updates they need to perform on their views. This mediation makes up a lot of the application code written in iOS apps.
Views should never have direct references to models and should only have references to controllers through IBAction events. Business logic that doesn’t pertain to the view itself has no business being there.
MVC缺点:
导致出现重量级视图控制器
缺少合适的位置来放置网络逻辑
不利于单元测试
各部分管理职责比较模糊
MVCS,通过把数据存储抽离,是对MVC的最简单的优化。
在MVVM(Model-View-ViewModel
)框架中,view与view controller连接在一起,可以把它们看成是一体的。
View-model is an excellent place to put validation logic for user input, presentation logic for the view, kick-offs of network requests, and other miscellaneous code. The one thing that does not belong in the view-model is any reference to the view itself. The logic in the view-model should be just as applicable on iOS as it is on OS X. (In other words, don’t #import UIKit.h in your view-models and you’ll be fine.)
现在视图控制器仅关注于用 view-model 的数据配置和管理各种各样的视图, 并在用户输入时让 view-model 获知并在需要时向上游修改数据. 视图控制器不需要了解关于网络服务调用, Core Data, 模型对象等。View model 会在视图控制器上以一个属性的方式存在. 视图控制器知道 view-model 和它的公有属性, 但是 view-model 对视图控制器一无所知. 你早就该对这个设计感觉好多了,因为我们的关注点在这儿被很好地分离。
View-model 从必要的资源(数据库, 网络服务调用, 等)中获取原始数据, 运用逻辑, 并处理成 view (controller) 的展示数据. 它(通常通过属性)暴露给视图控制器需要知道的仅关于显示视图工作的信息(理想地你不会暴漏你的 data-model 对象). 它还负责对上游数据的修改(比如更新模型/数据库, API POST 调用).
The View-model should not know anything about the UI that will bind to it.
The results of using MVVM, in my experience, is a slight increase in the total amount of code, but an overall decrease in code complexity. A worthwhile tradeoff.
If you look again at the MVVM diagram, you’ll notice that I’ve used the ambiguous verbs “notify” and “update”, but haven’t specified how to do that. You could use KVO
, like with MVC, but that can quickly become unmanageable. In practice, using ReactiveCocoa
is a great way to glue all the moving pieces together.
ReactiveCocoa由两大主要部分组成:signals (RACSignal) 和 sequences (RACSequence)。
signal 和 sequence 都是streams,他们共享很多相同的方法。ReactiveCocoa在功能上做了语义丰富、一致性强的一致性设计:signal是push驱动的stream,sequence是pull驱动的stream。
Signals only send 3 types of messages to their subscribers:
next : This is how we get updated values from the stream
error : This is sent anytime the signal could not complete
competed : Sent to let you know that there will be no more signals on the stream
一个signal的生命期由很多下一个(next)事件和一个错误(error)或完成(completed)事件组成(后两者不同时出现)。
What is a subscriber?
Simply put, a subscriber is the bit of code that is waiting for the signal to send along its values so it can do something with them. (It can also act on the “complete” and “error” events too).
RACSequence简化了集合的转换。
RACSequence *normalizedLongWords = [[words.rac_sequence
filter:^ BOOL (NSString *word) {
return [word length] >= 10;
}]
map:^(NSString *word) {
return [word lowercaseString];
}];
Notice that RAC only deals in objects, not primitives like BOOL.
VIPER(视图 (View) - 交互器 (Interactor) - 展示器 (Presenter) - 实体 (Entity) - 路由 (Routing) )。
VIPER 的主要部分是:
视图:根据展示器的要求显示界面,并将用户输入反馈给展示器。
交互器:包含由用例指定的业务逻辑。
展示器:包含为显示(从交互器接受的内容)做的准备工作的相关视图逻辑,并对用户输入进行反馈(从交互器获取新数据)。
实体:包含交互器要使用的基本模型对象。
路由:包含用来描述屏幕显示和显示顺序的导航逻辑。
应用在接入网络以后会变得更有用处,但是究竟该在什么时候联网呢?又由谁来负责启动网络连接呢?典型的情况下,由交互器来启动网络连接操作的项目,但是它不会直接处理网络代码。它会寻找一个像是 network manager 或者 API client 这样的依赖项。交互器可能聚合来自多个源的数据来提供所需的信息,从而完成一个用例。最终,就由展示器来采集交互器反馈的数据,然后组织并进行展示。
VIPER,通过View/VC/Wireframe/Presenter/Interactor/Data Manager 在纵向上完成完整的独立的业务用例。同时,通过Wireframe层的通信机制,完成不同业务之间的交互。纵向隔离后,只通过唯一一层进行交互,将使得代码更容易理解和组织。
交互器是一个普通NSObject
对象,在应用中代表着一个独立的用例。它具有业务逻辑以操纵模型对象(实体)执行特定的任务。交互器中的工作应当独立与任何用户界面,同样的交互器可以同时运用于 iOS 应用或者 OS X 应用中。
实体也是普通NSObject
对象,是被交互器操作的模型对象,并且它们只被交互器所操作。交互器永远不会传输实体至表现层 (比如说展示器)。不要诧异于你的实体仅仅是数据结构,任何依赖于应用的逻辑都应该放到交互器中。实体永远不会由交互器传输给展示器,取而代之,那些无行为的简单数据结构会从交互器传输到展示器那里。这就防止了那些“真正的工作”在展示器那里进行,展示器只能负责准备那些在视图里显示的数据。
展示器是一个主要包含了驱动用户界面的逻辑的普通NSObject
对象,它总是知道何时呈现用户界面。基于其收集来自用户交互的输入功能,它可以在合适的时候更新用户界面并向交互器发送请求。
视图是一个抽象的接口 (Interface),在 Objective-C 中使用协议被定义。一个 UIViewController 或者它的一个子类会实现视图协议。展示器不知道 UILabel,UIButton 等的存在,它只知道其中包含的内容以及何时需要显示。内容如何被显示是由视图来进行控制的。
在 VIPER 中,路由是由两个部分来负责的:展示器和线框。由于展示器包含了响应用户输入的逻辑,因此它就拥有知晓何时导航至另一个屏幕以及具体是哪一个屏幕的能力。而同时,线框知道如何进行导航。在两者结合起来的情况下,展示器可以使用线框来进行实现导航功能,它们两者一起描述了从一个屏幕至另一个屏幕的路由过程。