欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

iOS应用开发实战:探索工程化过程中的三层结构 - MVC与MVVM模式详解

最编程 2024-02-11 07:13:09
...

目录
一、三层架构
二、视图层架构
  1、MVC
  2、MVVM


一、架构分层


上一篇我们说到组件化开发是一个App最大一层的架构,那具体到了一个一个的业务组件里,比组件化开发小一点的架构就是三层架构。所谓三层架构就是指把项目中所有的类划分到不同的层,目的是使项目逻辑更加清晰,提升开发和维护的效率,我们一般会把项目分为三层:数据层、业务层、视图层。

  • 数据层一般就是对网络三方库的封装和对本地数据三方库的封装,调用数据层你就可以获取到rawData;
  • 业务层内部在调用数据层的API,业务层主要负责的是决定数据的来源是从网络请求还是本地数据,然后等着数据层给它返回rawData,把rawData处理成视图层能直接使用的Model、ModelArray等模型数据回调给视图层;
  • 视图层内部在调用业务层的API,视图层主要负责的是告诉业务层它想要什么数据(即想做什么业务)并传递一些必要的参数给业务层,然后等着业务层返回数据后直接拿着用就可以了。

注意:严格来讲,三层架构并不真正就比组件化开发小,它们俩只是从不同的角度去做架构而已,往往是相互融合的,比如组件化开发里网络库就是三层架构里数据层的内容,UI库就是三层架构里视图层里的内容等,这里之所以说“三层架构是比组件化开发小一点的架构,主要是针对业务组件来说的”,当然甚至我们在不使用组件化开发时项目里依然可以使用三层架构。


二、视图层架构


比三层架构再小一点的架构就是视图层架构,我们常说的MVC、MVVM都是˛视图层的架构。

1、MVC

苹果版MVC

MVC,即Model-View-Controller,它们的关系如下:

Model的职责:

  • Model只负责封装数据,不做任何其它操作。

View的职责:

  • View负责显示数据,那怎么显示数据呢?View需要把供外界使用的子组件给暴露出去;
  • View负责响应与业务有关的事件并交给Controller去处理,怎么交给Controller呢?通过代理、block、通知等;
  • View负责响应与业务无关的事件,如某些事件会引发动画效果等,这应该尽量在View内部完成。

Controller的职责:

  • Controller负责持有View,创建View,并把View添加到窗口上显示;
  • Controller负责加载Model,加载完后就持有Model并显示在相应的View上;
  • Controller负责监听View与业务有关的事件,并通过与Model的合作,来完成相应的业务;
  • Controller负责监听Model的变化,并通过与View的合作,来完成相应的业务。

苹果版MVC的优势就在于:

  • Model和View之间没有一点儿耦合,它们可以独立使用,具有高度复用性。

缺点也很明显:

  • Controller的代码过于臃肿,这主要体现在View的很多子组件暴露在外面,Model给View赋值的那个地方。
变种版MVC

变种版MVC是针对苹果版MVC的缺点进行的,变种的核心就在于:View持有了Model,把数据显示的逻辑放在了View内部来处理,而不再是通过View暴露子组件的方式来显示数据了。

变种MVC的优势就在于:

  • 在一定程度上为Controller瘦了身;
  • 将View内部的实现细节封装起来了,外界不需要知道View内部的实现细节,只需要给它一个Model就可以了。

缺点也很明显:

  • View明显耦合于Model,这个View无法再独立使用,必须绑定某个特定的Model一起使用。(不过我觉得这是可以接受的,毕竟我们既然选择了这种架构,就表明这种View专门就是用来绑定某个特定的Model显示的,而如果我们需要让一个View能显示各种Model,那可能就会选择苹果版MVC,也就是说实际开发中可能苹果版MVC和变种版MVC都有)

2、MVVM

但无论是苹果版MVC还是变种版MVC,它们都有一个致命的缺陷,那就是随着业务的增加,C会变得越来越臃肿,几千行的代码维护起来简直就是噩梦,这主要是因为C干了太多的事,比如获取数据、处理数据、存储数据等。

那有没有办法解决这种情况呢?经过多年的实践证明,MVVM是解决这种情况的有效办法。

MVVM,即Model-View-ViewModel,它们的关系如下:

MVVM就是把MVC里Controller里获取数据、处理数据、存储数据的操作,还有其它一堆业务逻辑的代码都放到了ViewModel里,从而达到为Controller瘦身的效果。

  • 所谓获取数据的操作是指调用业务层的API获取数据;
  • 所谓处理数据的操作是指ViewModel会针对Model一对一地创建外界真正使用到的属性、这些属性属性通常搞成getter方法就可以了、对Model的原始属性进行处理,以便外界能够直接使用;
  • 所谓存储数据的操作是指ViewModel会把获取并处理后的数据保存在自己内部供外界使用,而不是暴露给外界让Controller去持有;
  • 其它一堆业务逻辑的代码。

注意一个ViewModel里不是非要把这几件职责凑齐才配叫ViewModel,有其中的一部分也可以。

通常情况下ViewModel的创建主要有两个触发点:

1、为每个Model创建对应的ViewModel:只要接口返回的数据不是非常不符合主流的设计规范,那么如果接口返回的是字典,那这个字典绝对可以对应为Model,那么只要有一个Model,我们就得有与之对应的ViewModel来处理Model的数据————Model可能会有很多属性,但View层只会展示部分属性,而且有的属性得处理一下才能供View层直接使用,所以这个ViewModel就会针对Model一对一地创建外界真正使用到的属性、并对Model的原始属性进行处理以便外界能够直接使用、这些属性属性通常搞成getter方法就可以了(Swift、Dart直接搞getter方法就可以,OC得创建property然后再提供getter方法),此时这个ViewModel已经体现了ViewModel的模型性;这种情况下的ViewModel是适合添加获取数据、存储数据、处理其它业务逻辑的代码的,尽管去添,这样这个ViewModel就是具备了模型性和逻辑性的完整ViewModel;

2、ViewModel太小了不适合管更大的事、于是创建一个更大的ViewModel:但如果接口返回的是数组,一般都是数组内部的小字典才对应为一个Model,那当我们为这个Model创建对应的ViewModel之后,就意味着数组内的小字典才对应ViewModel内部的那个model属性,而我们又必然需要把接收到的model数组转换成viewModel数组,但是这个viewModel数组是不宜直接存储在ViewModel内部的,因为这个viewModel数组里存储的就是一个一个的ViewModel本身,而这个ViewModel的定位其实是对单个Model的一对一包装、然后顶多是处理一下跟单个Model有关的业务逻辑,那这时如果允许它内部出现一个存储了一堆它自己——即ViewModel——的数组,那就意味着这个ViewModel除了需要处理跟它一对一的那个model的业务逻辑,还需要处理一堆跟它并没有多大关系的model的业务逻辑,这显然违反了初衷,所以此时这个ViewModel最好不要多管闲事,只体现ViewModel的模型性就足够了;此时我们就得再创建一个更大一层的ViewModel来处理业务逻辑了————获取数据、存储数据(这个大ViewModel一般会持有第一步的小ViewModel所构成的数组)、处理其它业务逻辑的代码,其实这个触发点也是很好找的,一般就是第1个触发点创建出来的ViewModel只是一个Item对应的视图模型,不适合去管更大的事,而这个更大一层的ViewModel则是统筹全部Item的视图模型,而这个更大的ViewModel提现的更多的就是ViewModel的逻辑性。

综上,如果接口返回的是字典,我们创建一个ViewModel就够了,这个ViewModel既能体现模型性也能体现逻辑性(学生答题项目的接口设计是非常好的,基本就是这种情况,代码逻辑非常清晰);而如果接口返回的是数组,我们就得创建两个有关联的ViewModel了,小ViewModel专门处理数组里小字典的数据,着重体现模型性,大ViewModel专门处理获取数据、存储数据、处理其它业务逻辑的代码,着重体现逻辑性(智能白板项目的接口设计有的不是那么好,可能会出现这种情况,因为需要分两个ViewModel);当然如果随着业务的增加“一个ViewModel”或者“大ViewModel”里的代码越来越复杂越来越庞大,那我们也可以考虑按业务模块创建单独业务模块的ViewModel,然后让“一个ViewModel”或者“大ViewModel”去持有这些单独业务模块的ViewModel来做整体的业务调度。(智能白板里演讲模式模块、跟随模式模块、分布式数据存储模块就开始采用这种方式了)。

其实做完上面的操作就已经是MVVM了,但是业界经常会在上面MVVM的基础上再加个响应式编程,响应式编程可以为MVVM提供更加简单优雅的双向绑定,主要是指数据驱动UI,常用的响应式编程框架有RAC和RxSwift,可参看【RxSwift】RxSwift的理论知识【RxSwift】RxSwift在MVVM方面的实际应用