Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS开发架构的探索——不再是MVC #1

Open
leobxpan opened this issue Mar 27, 2016 · 8 comments
Open

iOS开发架构的探索——不再是MVC #1

leobxpan opened this issue Mar 27, 2016 · 8 comments

Comments

@leobxpan
Copy link
Member

iOS开发架构的探索——不再是MVC.pdf

@leobxpan
Copy link
Member Author

我虽然是以iOS App为例来讲解的,但是架构是适用于任何方向的,所以各个方向的同学们都来围观一下~里面用到了一些iOS开发的专用术语,如果不懂可以自行google,还不懂就直接comment~

@leobxpan
Copy link
Member Author

MVC这个词,大家想必对它再熟悉不过了,尤其是开发iOS App的童鞋们。不过,大家都知道MVC可以解读为Model-View-Controller,大家可还知道它还可以被解读为Massive ViewController(重量级视图控制器)?让我们先从这个梗的来历说起。
MVC架构是苹果官方推荐使用的一种架构,而苹果也在它的语言中体现了这一点——精心为你准备的UIViewController、UIView类,你直接拿来用就可以了。这个架构应用广泛、便于理解、易于使用,而且它是Apple推荐使用的喂!然而这并不能说明它就是一个很棒的开发架构,这要从MVC的分工说起。
image
M(Model)——程序中要操纵的实际对象的抽象,包含了代表着实际对象属性(例如汽车的颜色)的属性(property)和操作这些属性的方法(method),在MVC的相互协作中,View会通过Controller来向Model索要数据,经由Controller转换之后展示到View上,同时会将用户操作通过Controller反馈到Model中,更新Model的内容。而至于如何获取需要的数据,以及如何处理用户发出的数据请求,这些通常都定义在Model中。
V(View)——在标准的MVC模式中,View通常又被叫做“哑掉的View(Dumb View, 我自己翻译的==)”。也就是说View只负责机械地展示来自Model的数据,并且将用户的操作反馈到Controller中,自己并不参与到整个数据的处理过程当中。在iOS开发中,标签、文本框、表单、图片、滑动页等等这些都属于View这一类。
C(Controller)——可以说Controller是MVC中最重要的一部分,Controller负责从Model获取数据(Controller不直接持有Model,而是通过KVC或KVO获取数据),经过处理(将raw data处理为可以直接展示的数据形式,这个处理过程叫做表示逻辑,presentation logic)后交给View(Controller直接持有View)展示;同时,Controller负责对用户在View上的操作进行响应,并相应地更新Model。
我们可以看到Controller的功能何其强大!它做的事情实在太多了,导致在App不断扩展的时候,Controller的体积也变得越来越大。在一个小型App(比如天气预报)里可能看不出来,但是对于一个大型App来说,上万行的Controller是常有的事。如果一个模块做了太多的事情,你就要考虑一下你做的是否正确了。另外,由于Model、View、Controller三个部分之间的耦合非常紧密(tight coupling),导致对presentation logic的单元测试(Unit Test)几乎是不可行的——你只要实例化Controller就必须要同时实例化一堆与之相关联的View。从这几点我们可以看出,对于开发大型App来说,MVC已经不是一个合适的开发架构了。当然,如果你开发的是一个小型App,MVC仍然是你最好的选择,因为它够快、够简单。下面,我们来探索一些更佳的开发架构。

1、MV*架构
这种类型的开发架构实际上是在原有的Controller上下功夫,寻找Controller更佳的替代品。这种开发架构的代表是MVP(Model-View-Presenter)和MVVM(Model-View-ViewModel),在这里我只讲MVVM,因为MVP和MVVM并没有什么本质上的区别。

MVVM(Model-View-ViewModel)
之所以选择讲MVVM,是因为MVVM是MV_系列里目前来说最出色的一种架构,而且它很能代表MV_的思想,以至于其他MV_架构(比如MVP)可以表示成MVVM的一个简单的变体。现在我们就来看一下这个架构的强大之处,我们仍然从各个模块的分工讲起。
image
image
M(Model)——Model部分和MVC中的Model没有什么区别。
V(View)——注意!在MVVM中,View不再是UIView的子类,而变成了UIViewController的子类。因此我们可以在上图中看到,View实际是和ViewController绑定在一起的。这种View实际上就是MVC中剥离了处理presentation logic部分的Controller——它仍然有各种UIView_的属性(例如UILabel、UITextField等等)、仍然有ViewController生命周期的各种方法(因此View部分负责将视图展示出来,也负责响应用户的操作),但是它不知道该展示些什么数据(实际上MVC中的Controller也是不知道的),它也不知道该用什么方法处理并展示来自Model的数据(这个是MVC中的Controller知道的)。少做了一大部分工作之后,View(这里也是Controller)终于不再臃肿了。
VM(ViewModel)——在MVVM中,扮演协调者(Interactor)角色的不再是Controller,而变成了ViewModel。ViewModel被View持有,同时也持有着Model。ViewModel中定义了如何从Model获取数据、如何更新Model、如何处理用户的数据请求以及何时以何种方式更新View等等的众多方法,可以说是MVC中Controller的一个精简版。

在MVVM中,除了ViewModel比Controller做了更少的事情之外,各个模块之间的耦合也没有MVC里那么紧密的耦合。在MVVM中,View持有ViewModel,ViewModel持有Model。但是ViewModel没有反过来持有View,Model也没有反过来持有ViewModel。那么这两个方向的通信如何进行呢?更直接一点来说,我如何能让ViewModel告诉View它该更新了(以及以何种方式更新),又如何能让Model告诉ViewModel它(Model)发生了变化呢?对于后者,我们仍然可以采用KVC或者KVO的方式来进行通信。而对于前者,我们推荐使用一种通过发射信号(Signal)的方式来进行通信。在这方面有许多第三方库做得非常好,其中应用最为广泛的当属ReactiveCocoa。ReactiveCocoa是一个非常棒的通信框架,但我这里不打算详细介绍,请大家自行Google使用方法。
另外,由于获取数据的部分改由ViewModel来做,不再和View绑定(在MVC中是由和View绑定的Controller做的),因此单元测试变得十分容易,现在我们可以单独地测试对数据进行操作的部分,而不用担心会实例化一堆View了。

2、VIPER(View-Interactor-Presenter-Entity-Router)
VIPER是一种很有意思的架构,因为它把要处理的职责划分成了五层。实际上VIPER是MVVM的一个更细致的划分,让各个模块有了更清晰单一的分工。
在详细介绍VIPER前,我需要先来介绍一下用例(Use Case)的概念。
“Apps are often implemented as a set of use cases. Use cases are also known as acceptance criteria, or behaviors, and describe what an app is meant to do. Maybe a list needs to be sortable by date, type, or name. That’s a use case. A use case is the layer of an application that is responsible for business logic. Use cases should be independent from the user interface implementation of them. They should also be small and well-defined.”——Jeff Gilbert & Conrad Stoll
通俗来讲,用例就是指用户会用你的程序做的事情,一个事情成为一个用例。比如我点击了这个按钮,程序会有什么样的反应,这就是一个用例。
介绍完用例的概念,我们再来看看VIPER中各个模块的分工:
image
View:和MVVM中的View一样,它也是UIViewController的子类,它仍然负责将各种UI组件展示到屏幕上。但是与MVVM不同的是,它现在不负责响应用户对UI的操作了,这个响应部分现在被移到了展示器(Presenter)里。
Interactor(交互器):交互器负责处理用例中规定好的逻辑,它负责获取并处理数据,并将数据送给展示器(Presenter)。
Persenter(展示器):展示器负责处理presentation logic,还负责响应各种用户事件(比如按钮的点击)。
Entity(实体):实体仅仅是一个数据结构的定义,它定义了程序要处理的对象应该具有哪些属性,而没有定义处理这些数据的方法(这一点和MVC、MVVM中的Model不同)。用过Core Data的同学会更容易理解这个东西。
Router(路由):路由负责处理各种转场(Transition)逻辑,也就是用于实现导航功能。以前写在Controller里的presentViewController、performSegue等方法就可以放在这里来实现了。

通过对VIPER中各个模块分工的介绍我们可以发现,VIPER实际上是对MVVM的各个部分进行了进一步的细化,但又不仅仅是对MVVM进行了更细致的分割:View和Presenter是对MVVM中的View的进一步细化,同时Presenter又包揽了一部分ViewModel的工作(例如presentation logic的处理);I和E是对Model的细化,但Interactor又包含了一部分ViewModel的工作(例如根据用户的操作来更新Model/Entity);Router则又分担了View的一部分工作。所以VIPER相比MVVM是更为细致的划分,同时两者的各个模块又有着功能上的交叉。
另外,在VIPER中,Presenter对View的通信仍然可以使用ReactiveCocoa来进行,因为这个方向上的通信和MVVM实际上是一致的,都是在刷新UI。

讲到这里,我们这次对iOS开发架构的探索就告一段落啦~我虽然是以iOS应用为例来讲解这些架构的,但实际上这些架构可以用在其他各种客户端的开发上。Any idea or suggestion is welcome~

推荐文章:
Architecture:
iOS-architecture-patterns
MVVM:
MVVM
MVVM-with-ReactiveCocoa
MVVM
VIPER:
VIPER

                                                                                                                                                                                                                                                                                                        By Caesar

@YunaQiu
Copy link
Contributor

YunaQiu commented Mar 28, 2016

今天上午才看到柏晓的这篇文章,其实刚看完的时候我脑子里是有点乱的。因为总觉得有些地方与我所熟悉的网页开发不好对应起来。于是在忙活着查了一早上后,算是从另一层面对MVC/MVP/MVVM有自己的一点新的理解,当然我也不太确定这些理解是否恰当,还请熟悉的大神们指点。另外我本身玩的是前端开发,主导的也是网页项目,所以可能会更多的从网页开发和前端开发的角度理解说明。
-------------------------此处是分割线-------------------------

MVC

此处的MVC是指最初的MVC开发模式,就如同柏晓前面展示的图片一样。在MVC模式中一般的流程是这样:
用户在VIEW(界面)触发事件,VIEW把事件请求提交给Controller=》Controller负责处理业务(事件),并将需要进行的数据更新操作通知给Model=》Model更新数据=》view通过向model请求数据来获取被更新后的数据,从而展现给用户。
也就是说,在传统的MVC模式中,View是可以直接向Model请求数据的。这导致了一部分的数据处理逻辑被放在了View层,而M-V-C三方的交互也带来了强耦合性的问题,这使得很多时候一些小的改动需要同时改三层,而由于view层对model的依赖也导致了view层可重用性的下降。
注意:这里说的MVC是指原始的MVC开发模式。而事实上,现在的MVC(尤其是在网页开发中)已逐渐向MVP模式转变。这也是为啥我一开始会觉得对应不起来,因为在我所熟悉的,我们部门现在采用的MVC框架开发中,是不存在view与model层的交互关系的。

MVP

或许对于网页开发的同学来说,会感觉对MVP模式更加的熟悉。这里先放张MVP的示意图

其中,Presenter对应了原来的controller部分。可以看到,在MVP当中,彻底切断了view与model之间的联系。用户依旧通过view触发事件,依旧是controller处理业务事件,再去更新model的数据。不同的就是view层所有的数据来源,都通过定义好的接口(图中圆圈部分),向controller层获取的。controller会将从model拿到的数据通过接口送给view,从而展示到界面上。在实际的开发中,view和controller根据已规定好的接口协议进行各自的开发,从而降低耦合度。

MVVM

首先在此表示,柏晓提到的viewController,我并没有在网上查到相关的概念理解啊,我所查到的架构都是类似下面这张图这样的,不知道viewController是不是iOS开发里面自己搞出来的一个东西。

个人觉得,将MVVM与MVP放在一起对比会更容易有对比性一点。在我的理解中,MVVM与MVP最大的不同就在View跟viewModel(对应MVC中的controller)之间的交互。MVP是通过定义接口来进行数据交流的,而MVVM则是直接通过数据的双向绑定来传递数据,在前端中典型的MVVM框架就是angularJS。(据说最典型的应用是WPF,然而我并不认识它┑( ̄Д  ̄)┍)
关于数据绑定,我大概的理解是这样。在通过接口交流数据的方式里,如果你的数据形式,或者数据内容需要变更,那么你可能需要为这种变更定义相关的事件处理,可能需要对接口做出修改,可能在view和controller(presenter)中也要做出一定的调整。而数据绑定则是改其中一个模块的数据,与其相关联的模块都会自动同步调整,使得开发人员不用过多的关注数据的更新和同步问题。这点在需要数据频繁更新,或者数据被多个模块同时引用的项目中尤其有优势。

@BillBai
Copy link
Member

BillBai commented Mar 28, 2016

MV* 其实就是把 MVC 中的 C 不断的拆分

@BillBai
Copy link
Member

BillBai commented Mar 28, 2016

MVC 中的一个问题是在于数据流的流向不统一,C 需要不断的处理来自 V 的数据(用户的输入等)以及 M 的数据(API返回的数据等),并需要维护 V 和 M 的状态(更新界面,网络请求,修改数据库等等),维护的状态越多,代码就越复杂,并且这种代码一定是偏向于命令式而不是声明式的。这样的话,代码的维护成本就会大大提升,也给了 bug 可乘之机。后来提出的 MVVM,函数响应式编程(FRP),常和react 一起用的 flux 以及 redux 等等,强调的都是单向数据流动以及数据绑定,来减少需要维护的状态的数量,并且使用数据绑定等机制来减少命令式的修改状态的代码。

MVC 中另一个问题在于 C 很容易堆积过多的代码,最初引入 C 的一个重要目的是为了达到表现(V)与数据(M)的分离,然而随着业务的增长,很容易就将各种代码堆到 C 中了,毕竟 C 通常是持有(own) V 和 M 的,最终 MVC 就成了 “Massive ViewController”。。。 这明显是有违单一职责原则(SRP)的。VIPER 就是根据这一点,细分了 C 的职责。

@BillBai
Copy link
Member

BillBai commented Mar 28, 2016

最后说一下 iOS 中的 ViewController,其实这个虽然我们通常把它当作 C,但是它和 V 的关系更紧密一些。因为 iOS 客户端中,ViewController 其实一个主要的任务是要管理 V 的生命周期的(想想 viewDidiLoad,viewWillAppear,viewWillDisappear 等等)。

然而在 web 框架中,Controller 并不需要管理 V 的生命周期,因为生成的 HTML、CSS、JS 等等直接就丢给客户端了。。。

@Zhangjd
Copy link
Member

Zhangjd commented Mar 28, 2016

客户端开发属于 C/S 架构,Web 开发属于 B/S 架构,导致有一些区别。

  1. Web 开发中,URL 的改变对 View 会造成影响,这里要分开讨论。传统的 WebPage 构建中,前端不需要考虑 router 这一层面,在页面跳转时,reload 整个页面,浏览器向后端请求新页面内容。如今的前端涌现了 Single Page App 之类的 WebApp 架构,为了改善用户体验,进行页面局部刷新,会拦截跳转事件,才会用前端的 router 去作控制。WebApp 的发展导致了前端 MV* 模式的潮流。
  2. 前端开发的 MVC 架构核心是事件流。绑定 这个词在前端开发中经常提及,就是因为 DOM event 会对 View 造成影响。

P.S. Angular 属于 MV* 框架,以下摘自 README

MVC, no, MV* done the right way!
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-Whatever. The Whatever is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
Unlike other frameworks in any programming language, where MVC, the three separate components, each one has to be written and then connected by the programmer, AngularJS helps the programmer by asking him/her to just create these and everything else will be taken care of by AngularJS.

当然,用上 Two-way Data Binding,这个 * 更接近是 ViewModel。

@jingkecn
Copy link
Member

作為一個巨硬粉,我想說 MVVM 這等好東西是巨硬工程師為 WPF 設計的,MSDN 上面有非常多非常好的資源可以理解 MVVM,比如:https://blogs.msdn.microsoft.com/msgulfcommunity/2013/03/13/understanding-the-basics-of-mvvm-design-pattern/
話說 Angular 的那個所謂的 MVVM 看著感覺不太正統(不過 TypeScript 版的 Angular 2.0 貌似會改進)
多嘗試一些經典的 pattern 吧,不要再局限於 MVC (我一直很反對 developer 將其奉為神明,其實只是不思進取的藉口),多去了解觀察者模式、單例模式等

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants