16 | 架构模式:为什么要选择 MVVM 模式?

[toc]

前言

本文来自拉勾网课程整理

作为iOS开发者,我们都很熟悉MVC模式。根据苹果官方的解释, MVC 表示 Model-View-Controller, 也就是模型、视图和控制器。但是业界一直把MVC戏称为 Massive ViewController(臃肿的视图控制器)。因为当我们使用 MVC的时候,随着功能越来越丰富, ViewController 往往变得臃肿和繁杂,而且模块之间相互耦合,难以维护。下图显示了苹果的 MVC 模式。

070bcbf228042085ed88a07d5e946780

其中,Controller 通常指 ViewController,是 MVC 的核心,ViewController通过Target-ActionDataSourceDelegate 来接收来自 View 的用户事件,并通过 Outlet 来更新 View。同时ViewController 还通过 NotificationKVO 来接收来自 Model的通知,并通过变量来更新 Model

除了与 ViewModel 进行交互以外,ViewController 还负责导航、网络访问、数据缓存、错误处理以及 Model 对象的EncodeDecode。由于 ViewController承担多项责任,往往导致代码量极大,且由于强耦合,对 ViewController 的一点点改动都需要进行手工回归测试,费时费力。那么,有没有什么好的办法来解决这些问题呢?

MVVM 及其优点

经过多年实践证明,MVVM 模式是目前解决臃肿 ViewController问题的有效方法。MVVM全称 Model-View-ViewModel(模型-视图-视图模型)。与 MVC相比,它引入了一个名叫ViewModel的新概念。如下图所示。

bc46ab6f120f501478f17821f6ae5da2

MVVM由三层组成:

Model,用于保存数据的模型对象,通常定义为只有数据并没有方法的结构体(Struct);

View,用于呈现 UI(用户界面)并响应用户的事件,通常是 ViewControllerView

ViewModel,用于桥接ModelView两层,把 Model转换成 View呈现 UI所需的数据,并把 View 层的用户输入数据更新到 Model中。

那么,和 MVC相比,MVVM 模式具有什么优点呢?具体有以下几个。

  • 能效减低代码的复杂性,即 MVVM 模式能有效简化 ViewController 的逻辑,使得 ViewController 的代码只处理 UI 相关的逻辑。
  • 能提高代码的可测试性,由于 ViewModel 明确负责 ModleView 之间的数据转换,而且不负责 View 的生命周期管理,我们可以很方便地测试 ViewModel 的代码逻辑,提高代码的健壮性。
  • 能够减低代码耦合性。在 MVVM 模式下,每一层都明确分工,这样可以减少代码耦合性,提高代码的可维护性和可重用性。
  • 减轻移动团队的开发门槛,方便知识的共享。前两年谷歌公司为 Android 引入了一个基于 MVVM 模式的新框架 Architecture Components,使用 MVVM模式能方便开发者同时在iOSAndroid 两个平台开发功能。

基于 MVVM 的架构设计

如今,经过多年的实践,我们已经能够成功地使用 MVVM模式在 iOSAndroid上实现了一套风格一致的架构设计。 比如,在 Moments App里面,我就进行了优化并重新实现了一套基于MVVM模式的架构,具体如下图所示:

c40bb1e334ea9605a604e5877702f461

Moments App 的架构详解

以上是我们 Moments App 的架构图,下面我把每一层进行分解和介绍下。

View 层

View 层由UIViewController以及UIView所组成,负责呈现UI和响应用户事件。由于 MVVM 模式相当灵活,我们在后面会介绍如何在保持其他模块完全不变的情况下把View层换成 SwiftUI 来实现。

ViewModel 层

它是 MVVM 模式的核心,主要任务是连接 ViewModel层,为 View层准备呈现UI所需的数据,并且响应 View 层所提供的用户事件。同时它还负责处理路由和发送用户行为数据。由于 ViewModel层的责任还是很重,因此我们把它进一步细分为各个模块,大致包括ViewModel、Routing、Tracking、Repository、Networking、DataStore

其中,ViewModel扮演一个协助者的角色,负责准备 View 层所需的数据,并响应 View 层的用户事件。ViewModel 与上层的UIViewControllerUIView之间通过响应式编程的方式来通知对方,而上层 UI组件通过数据绑定的方式,来监听 ViewModel 的数据变化,并及时更新 UI

Routing是负责路由和导航的模块,ViewModel 在响应 View 的请求时(如打开新页面),会调用 Routing 模块进行导航。

Tracking则负责统计分析数据的模块,ViewModel在响应 View 的请求时(如用户点击了点赞按钮),会调用 Tracking 模块来发送用户行为的数据。

Repository是数据层,作为唯一信息来源(Single source of truth)维护着 App所使用的Model 数据。当 ViewModel需要访问数据的时候,会调用 Repository 模块,然后Repository会根据需要通过 Networking 来访问网络的后台数据,或者调用 DataStore 来获取本地缓存的数据。ViewModelRepository的接口也是响应式编程的方式,主要由 ViewModelRepository发起请求,然后监听 Repository来获取数据所发生的变化。

Networking是网络层,负责访问 BFF,然后把 BFF 返回的 JSON数据 DecodeModel数据。RepositoryNetworking 的接口也是响应式编程的方式。Repository 会给 Networking 发起请求,并监听 Networking 的返回。

DataStore为数据存储层,用于把数据缓存到App里面使得用户在不需等待网络请求的情况下可以快速看到 UIRepositoryDataStore 的接口也是响应式编程的方式。Repository 监听 DataStore 的数据变化。

Model 层

最后一层是 Model层,它比较简单,都是一些用于存放数据的模型对象,这些对象能用于存放网络请求使用的数据,也可用于存放本地缓存的数据。

朋友圈模块的架构设计

有了上述的架构设计,我就可以把MVVM模式应用到各个业务模块里面。比如,在这里我以 Moments App的朋友圈模块作为例,和你介绍下它的具体的架构。下面是我为该模块所绘制的类型关系图。

5938373470e8a4022575484baa56321d

View 层用于显示 UIMomentsViewTimelineViewController用于显示朋友圈界面,这个页面里面使用了一个 TableView 来显示各种不同的Cell。这些Cell都通过UIView来显示,其中UserProfileListItemView用于显示用户个人的信息,而MomentListItemView显示一条朋友圈的信息。

ViewModel 层由多个组件所组成。其核心是MomentsTimelineViewModel,它为MomentsViewTimelineViewController准备呈现UI的数据,并处理用户的事件。各个UI子组件也对应着各个子 ViewModel。例如UserProfileListItemView对应了UserProfileListItemViewModel

MomentsTimelineViewModel要发送统计分析数据的时候会调用TrackingRepo,进而把用户行为数据发送到分析数据的后台。当MomentsTimelineViewModel要导航到其他页面的时候会调用AppRouter,而AppRouter会调用路由模块来导航到相应的页面去。

MomentsTimelineViewModel需要读取或者更新数据的时候,会给MomentsRepoo发起请求。该 Repo 负责发送网络请求并把朋友圈数据缓存到本地文件系统中。这个 Repo 还是所有朋友圈信息的数据中心,App 里面任何页面需要朋友圈信息的数据,都可以从该 Repo 中进行读取。

为了进一步解耦,我们把数据缓存和网络请求模块从Repo中独立出来,分成 DataStoreNetworking两个模块。例如当MomentsRepoo需要读写缓存的时候,会调用UserDefaultsPersistentDataStoreDataStore模块负责把模型数据保存到UserDefaults里面。

而当MomentsRepoo需要从网络上取出朋友圈信息时会调用GetMomentsByUserIDSessionGetMomentsByUserIDSession会从 BFF 里读取当前用户所有的朋友圈信息。当MomentsRepoo需要更新朋友圈信息时(如更新点赞的状态),会调用UpdateMomentLikeSession来对 BFF 发起更新的请求。

Model 层相对比较简单,只有一个用于保存朋友圈信息的模型数据:MomentsDetails。其中UserDefaultsPersistentDataStore使用它来进行本地缓存的读取,而GetMomentsByUserIDSessionUpdateMomentLikeSession会使用它来存放网络请求的JSON 数据。

上面就是 Moments App 基于 MVVM模式的架构设计,我们会在后面几章中详细介绍各个层的架构与具体实现方式。

总结

结合 Moments App· 介绍了一套可行的 ·MVVM· 架构模式。通过它,我们可以有效降低各个模块之间的耦合度,提高可重用性,再加上由于每个模块有了明确的责任与分工,我们在实现新功能时,也能降低沟通成本,提高开发效率。所以有关这个架构模式,你一定要重视起来哦。

另外,结合BFF 的后端服务,我们还可以在iOSAndroid两端同样使用 MVVM模式来进行架构设计,并保持一致的代码风格,以便于开发者能同时在两个平台进行开发。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容