不是我说,MVC & MVVM 设计模式其实不难,搞定工作没问题
简介
iOS开发中,MVC是构建iOS App标准模式,苹果推荐的一个用来组织代码的权威范式。现在,MVC仍然是主流客户端变成框架,但同时也被调侃成Massive View Controller(重量级视图控制器),有以下几个问题:
● 厚重的ViewController
● 遗失的网络逻辑(无立足之地)
● 较差的可测试性
因此,为了避免和解决上述问题,从MVC中引申出来一种维护性较强、耦合性低的新架构MVVM(Model View View-Mode),其正式规范了视图和控制器紧耦合的性质,并引入新组件。MVVM主要目的是为了分离视图(View)和模型(Model)。
MVC
MVC之间关系
典型的MVC架构不适用于当下的iOS开发,尽管从技术上看View和Controller相互独立,但事实上它们几乎总是结对出现,一个View只能与一个Controller匹配,反之亦然。故可正规化它们连接:
因此,M-VC可能是对MVC架构更为准确的解读,也更准确的描述了日常开发编写的MVC代码,但它并没有做太多事情来解决iOS应用中日益增长的重量级视图控制器的问题。
MVC 弊端
● 厚重的ViewController:
M:Model对象通常非常简单,官方定义,Model应该只包括数据和操作数据的业务逻辑。但在实践中,Model层往往非常薄,不管怎样, Model层的业务逻辑不应被拖入到Controller。
V:视图View通常是UIKit空间或者编码定义的UIKit控件的集合。View的构建不需Controller,View也不应该直接引用Model(理论上)。
C:协调Mode和View的所有交互,Controller负责管理他们所拥有的的视图的视图层次结构,还要响应视图的loading,appearing,disappearing等,同时也往往会充满我们不愿暴露的Model模型逻辑和不愿意暴露给视图的业务逻辑。
网络数据的请求以及后续处理,本地数据库操作,以及一些带有工具性质辅助方法都加大了Massive ViewController的产生)
● 遗失(无处安放)的网络逻辑:
由于网络调用应该使用异步,若网络逻辑放在Model中,一个网络请求有可能比持有它的Model生命周期更长,事情将变得复杂。View里面就更加格格不入。若又放在Controller中,则加剧Massive ViewController问题。
● 较差的可测试性:
由于 View Controller 混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。若一个Massive View Controller 有上万行代码,要你编写单元测试,那并不有趣。
MVVM
升级
一种可以很好解决Massive ViewController 问题的办法就是将Controller中展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是viewModel。MVVM衍生于MVC,促进了UI代码与业务逻辑的分离,正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:
基本概念
● MVVM中,view和ViewController正式联系在一起,把它们视为一个组件。
● view 和 ViewController都不能直接引用model,而是引用视图模型ViewModel。
● ViewModel是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码的地方。
● 使用MVVM会轻微增加代码量,但总体上减少了代码的复杂性。
注意事项
● view 引用 ViewModel,但反过来不行(即不要在ViewModel中引入UIKit,任何视图本身的引用都不应该放在ViewModel中)。 基本要求,必须满足
● ViewModel引用Model,反过来不行。
使用建议
● MVVM可以兼容当下使用的MVC架构。
● MVVM增加你的应用的可测试性。
● MVVM配合一个绑定机制效果最好
● ViewController尽量不涉及业务逻辑,让ViewModel处理这些事情
● ViewController只是一个中间人,接收View事件,调用ViewModel方法,响应ViewModel变化
● ViewModel绝对不能包含视图,不然就会跟View产生耦合,不方便复用和测试
● ViewModel之间可以有依赖
● ViewModel避免过于臃肿,否则重蹈Controller的覆辙,变得难以维护
优势
● 低耦合---View可以独立于Model变化和修改, 一个ViewModel可以绑定到不同的View上
● 可重用性---可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑
● 独立开发---开发人员可以专注于业务逻辑和数据的开发ViewModel,设计人员可以专注于页面设计
● 可测试--- 通常界面是比较难以测试的,而MVVM模式可以针对ViewModel来进行测试
弊端
● 数据绑定使得bug难被调试,发现界面异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得一个位置的Bug快快速传递到别的位置,要定位原始出问题的地方就变得困难
● 对于过大的项目,数据绑定和数据转化需要花费更多的内存(成本)。
MVVM Without ReactiveCocoa 设计模式
回顾MVVM
现看张更详细的MVVM 结构图
Model: 和MVC中model保持一致,你可能为model封装一些额外的操作数据的业务逻辑,苹果推崇你这么干。但是,这可能导致一个“胖model”的诞生,带来的问题是胖mode相对难移植,随着产品迭代增加fat,最终难以维护。更好的事把它当作一个容纳表现数据模型对象信息的结构体,并通过一个单独的管理类来维护/创建/管理模型的统一逻辑。MVVM是基于胖model架构思路建立的,然后在胖model中拆出两部分:model 和 ViewModel。
View|Controller:由MVC中View 和 ViewController组成,负责UI的展示,绑定ViewModel中的属性,触发ViewModel中的命令以及呈现由ViewModel提供的数据。补:MVVM的推出很大程度是对ViewController的“瘦身”,将其中的业务逻辑剥离到ViewModel中,MVVM严格上是MVMCV, Controller在MVVM中,一方面负责View和ViewModel之间的绑定,另一方面也负责常规的UI逻辑处理。
View-Model: 并非传统数据-模型结构中模型,它的职责之一是作为一个表现视图显示自身所需数据的静态模型。但它也有收集,解释和转换那些数据的责任。它是从MVC的ViewControlle中抽取出来的展示逻辑,负责从Model中获取View所需的数据,转换成View可以展示的数据,并暴露公开的属性和命令供View进行绑定。
Binder:隐含组件。MVVM中,声明式的数据和命令绑定是一个隐含的约定,它可以让开发者非常方便地实现View和ViewModel的同步,避免编写大量繁杂的样版化代码。MVVM实现中,用ReactiveCocoa来在View和ViewModel之间充当binder的角色,实现两者之间的数据绑定。
附MVVM模块层级图:
前期准备
使用MVVM搭配ReactiveCocoa会很优雅地实现View和ViewModel之间的数据绑定,不过它的问题在于学习成本和维护成本比较高,但是切记:MVVM的关键是要有ViewModel!而不是ReactiveCocoa。
RAC是基于KVO构建的,也可以用KVO来让View获取ViewModel变化。但KVO糟点多:需要注册成为某个对象属性的观察者,还要在合适的时间点把自己移除,再加上需要覆写又臭又长的方法,并在方法里判断这次是不是自己要观测的属性发生了变化等, 可以用Facebook开源的KVOController,它比较优雅的处理了KVO存在的一些问题,同时又能发挥KVO带来的便捷性。
实例(二手交易平台)
登录界面
逻辑图
ViewModel的设计
显然,ViewModel仅仅只暴露了视图控制器所必需的最小量的信息,设置read-only属性很有必要。同时,试图控制器实际上并不在乎ViewModel是如何获得这些信息的。切记: ViewModel千万不用主动对视图控制器以任何形式直接起作用或直接通告其变化,而是等待视图控制器来主动获取。
而View绑定ViewModel的绑定、监听设计成block回调来减少状态的处理
ViewController 设计
ViewController的登录按钮被点击时,调用ViewModel上的login方法,同时ViewController通过KVO方法监听executing、error、responseObject的属性即可。但KVO代码量过大,可采用block 回调方式实现
● 视图控制器从viewModel获取数据,当validLogin值发生变化时,触发登录按钮的enabled 属性,并监听avatarUrlString 变化,来更新视图控制器的头像的UIImageView
● 视图控制器也对viewModel起作用,当UITextField 文本发生变化,更新viewModel上的readwrite属性,登录按钮被点击时,调用viewModel上的loginSuccess:failure 方法
● 视图控制器不需要做的事有:发起登陆的网络请求,判定登录按钮的有效性,获取头像的地址(有可能从本地数据库获取,也有可能通过网络请求来获取)。。。。。。
● 注:视图控制器总的责任是处理viewModel中的变化
首页界面
ViewModel的设计
ViewController 的设计
视图控制器通过调用ViewModel的loadBannerData:failure: 和 loadData:failure:configFooter:来获取商品首页的广告数据以及商品数据。通过使用ViewModel上的banners和 dataSource数组中的对象来配置tableView的tableViewHeader和cell。
MVVM With ReactiveCocoa
简介
iOS开发中,系统没有提供类似的框架可以让我们方便地实现binder功能,不过,值得庆幸的是,GitHub有开源的RAC(ReactiveCocoa),给了我们一个非常不错的选择-----
MVVM使用中,通常会利用双向绑定技术,使得Model变化时,ViewModel会自动更新,ViewModel变化时,View也会自动更新,MVVM开发中可以使用RAC来充当binder,实现二者的数据同步。
RAC是一个iOS中的函数式响应式编程框架,是开发GitHub for Mac的一个副产品,提供一系列用来组合和转换值流的API。
注:函数响应式编程---
当c=a+b定义好后,a发生变化后,会产生一个信号,这个信号通知c根据a变化的值来变化自己的值,b同理,这就是函数响应式编程。
iOS的MVVM实现中,我们可以使用RAC在view和ViewModel之间充当binder的角色,优雅地实现两者之间的同步。此外,我们还可以把RAC用在Model层,使用signal来代表异步的数据获取操作,比如读取文件访问数据库和网路请求等。
说明,RAC的后一个应用场景是与MVVM无关的,也就是说,我们同样可以在MVC的model层这么用。
RAC作用
● 提供一个单一的、统一的方法去处理异步行为,包括Delegate,Blocks,Notification,KVO。。。
● RAC将Cocoa中KVO,UIKit Event,Delegate等都增加了RAC支持,所以不用做很多跨函数的事,而且利用RAC处理事件很方便,可以把要处理的事情和监听的事情的代码放在一起,方便我们管理,就不需要跳到对应的方法里,符合我们开发中高聚合,低耦合的思想。
RAC核心
RAC核心就是RACSignal,RACSignal对于RAC来说是构造单元,代表我们最终将要收到的信息,表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据,所以你可以开始预先运用逻辑并构建你的信息流,而不是必须等到事件发生。
信号会为了控制通过应用的信息流而获得所有这些异步方法(委托,回调block,通知,KVO),并将它们统一到一个接口下。不仅这样,因为信息会流过你的应用,它还提供给你轻松转换、分解、合并、过滤信息的能力。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了