[toc]
前言
本文来自拉勾网课程整理
作为iOS
开发者,我们都很熟悉MVC
模式。根据苹果官方的解释, MVC
表示 Model-View-Controller
, 也就是模型、视图和控制器。但是业界一直把MVC
戏称为 Massive ViewController
(臃肿的视图控制器)。因为当我们使用 MVC
的时候,随着功能越来越丰富, ViewController
往往变得臃肿和繁杂,而且模块之间相互耦合,难以维护。下图显示了苹果的 MVC
模式。
其中,Controller
通常指 ViewController
,是 MVC
的核心,ViewController
通过Target-Action
、DataSource
和 Delegate
来接收来自 View
的用户事件,并通过 Outlet
来更新 View
。同时ViewController
还通过 Notification
和KVO
来接收来自 Model
的通知,并通过变量来更新 Model
。
除了与 View
和 Model
进行交互以外,ViewController
还负责导航、网络访问、数据缓存、错误处理以及 Model
对象的Encode
和Decode
。由于 ViewController
承担多项责任,往往导致代码量极大,且由于强耦合,对 ViewController
的一点点改动都需要进行手工回归测试,费时费力。那么,有没有什么好的办法来解决这些问题呢?
MVVM 及其优点
经过多年实践证明,MVVM
模式是目前解决臃肿 ViewController
问题的有效方法。MVVM
全称 Model-View-ViewModel
(模型-视图-视图模型)。与 MVC
相比,它引入了一个名叫ViewModel
的新概念。如下图所示。
MVVM
由三层组成:
Model
,用于保存数据的模型对象,通常定义为只有数据并没有方法的结构体(Struct
);
View
,用于呈现 UI
(用户界面)并响应用户的事件,通常是 ViewController
和 View
;
ViewModel
,用于桥接Model
和View
两层,把 Model
转换成 View
呈现 UI
所需的数据,并把 View
层的用户输入数据更新到 Model
中。
那么,和 MVC
相比,MVVM
模式具有什么优点呢?具体有以下几个。
- 能效减低代码的复杂性,即
MVVM
模式能有效简化ViewController
的逻辑,使得ViewController
的代码只处理 UI 相关的逻辑。 - 能提高代码的可测试性,由于
ViewModel
明确负责Modle
与View
之间的数据转换,而且不负责View
的生命周期管理,我们可以很方便地测试ViewModel
的代码逻辑,提高代码的健壮性。 - 能够减低代码耦合性。在
MVVM
模式下,每一层都明确分工,这样可以减少代码耦合性,提高代码的可维护性和可重用性。 - 减轻移动团队的开发门槛,方便知识的共享。前两年谷歌公司为
Android
引入了一个基于MVVM
模式的新框架Architecture Components
,使用MVVM
模式能方便开发者同时在iOS
和Android
两个平台开发功能。
基于 MVVM 的架构设计
如今,经过多年的实践,我们已经能够成功地使用 MVVM
模式在 iOS
和 Android
上实现了一套风格一致的架构设计。 比如,在 Moments App
里面,我就进行了优化并重新实现了一套基于MVVM
模式的架构,具体如下图所示:
Moments App 的架构详解
以上是我们 Moments App
的架构图,下面我把每一层进行分解和介绍下。
View 层
View
层由UIViewController
以及UIView
所组成,负责呈现UI
和响应用户事件。由于 MVVM
模式相当灵活,我们在后面会介绍如何在保持其他模块完全不变的情况下把View
层换成 SwiftUI
来实现。
ViewModel 层
它是 MVVM
模式的核心,主要任务是连接 View
和 Model
层,为 View
层准备呈现UI
所需的数据,并且响应 View
层所提供的用户事件。同时它还负责处理路由和发送用户行为数据。由于 ViewModel
层的责任还是很重,因此我们把它进一步细分为各个模块,大致包括ViewModel、Routing、Tracking、Repository、Networking、DataStore
。
其中,ViewModel
扮演一个协助者的角色,负责准备 View
层所需的数据,并响应 View
层的用户事件。ViewModel
与上层的UIViewController
和UIView
之间通过响应式编程的方式来通知对方,而上层 UI
组件通过数据绑定的方式,来监听 ViewModel
的数据变化,并及时更新 UI
。
Routing
是负责路由和导航的模块,ViewModel
在响应 View
的请求时(如打开新页面),会调用 Routing
模块进行导航。
Tracking
则负责统计分析数据的模块,ViewModel
在响应 View
的请求时(如用户点击了点赞按钮),会调用 Tracking
模块来发送用户行为的数据。
Repository
是数据层,作为唯一信息来源(Single source of truth
)维护着 App
所使用的Model
数据。当 ViewModel
需要访问数据的时候,会调用 Repository
模块,然后Repository
会根据需要通过 Networking
来访问网络的后台数据,或者调用 DataStore
来获取本地缓存的数据。ViewModel
和 Repository
的接口也是响应式编程的方式,主要由 ViewModel
给Repository
发起请求,然后监听 Repository
来获取数据所发生的变化。
Networking
是网络层,负责访问 BFF
,然后把 BFF
返回的 JSON
数据 Decode
成Model
数据。Repository
与 Networking
的接口也是响应式编程的方式。Repository
会给 Networking
发起请求,并监听 Networking
的返回。
DataStore
为数据存储层,用于把数据缓存到App
里面使得用户在不需等待网络请求的情况下可以快速看到 UI
。Repository
与 DataStore
的接口也是响应式编程的方式。Repository
监听 DataStore
的数据变化。
Model 层
最后一层是 Model
层,它比较简单,都是一些用于存放数据的模型对象,这些对象能用于存放网络请求使用的数据,也可用于存放本地缓存的数据。
朋友圈模块的架构设计
有了上述的架构设计,我就可以把MVVM
模式应用到各个业务模块里面。比如,在这里我以 Moments App
的朋友圈模块作为例,和你介绍下它的具体的架构。下面是我为该模块所绘制的类型关系图。
View
层用于显示 UI
,MomentsViewTimelineViewController
用于显示朋友圈界面,这个页面里面使用了一个 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
中独立出来,分成 DataStore
和 Networking
两个模块。例如当MomentsRepoo
需要读写缓存的时候,会调用UserDefaultsPersistentDataStore
,DataStore
模块负责把模型数据保存到UserDefaults
里面。
而当MomentsRepoo
需要从网络上取出朋友圈信息时会调用GetMomentsByUserIDSession
。GetMomentsByUserIDSession
会从 BFF
里读取当前用户所有的朋友圈信息。当MomentsRepoo
需要更新朋友圈信息时(如更新点赞的状态),会调用UpdateMomentLikeSession
来对 BFF
发起更新的请求。
Model
层相对比较简单,只有一个用于保存朋友圈信息的模型数据:MomentsDetails
。其中UserDefaultsPersistentDataStore
使用它来进行本地缓存的读取,而GetMomentsByUserIDSession
和UpdateMomentLikeSession
会使用它来存放网络请求的JSON
数据。
上面就是 Moments App
基于 MVVM
模式的架构设计,我们会在后面几章中详细介绍各个层的架构与具体实现方式。
总结
结合 Moments App· 介绍了一套可行的 ·MVVM· 架构模式。通过它,我们可以有效降低各个模块之间的耦合度,提高可重用性,再加上由于每个模块有了明确的责任与分工,我们在实现新功能时,也能降低沟通成本,提高开发效率。所以有关这个架构模式,你一定要重视起来哦。
另外,结合BFF
的后端服务,我们还可以在iOS
和Android
两端同样使用 MVVM
模式来进行架构设计,并保持一致的代码风格,以便于开发者能同时在两个平台进行开发。