APP 架构模式基础
App 的本质是反馈回路
GUI(图形用户界面 Graphical User Interface,简称 GUI,又称图形用户接口)应用是一个同时负责输入和展示功能的反馈设备,每个设计模式所面临的挑战都是如何处理这张图上所包含的交流依赖和变换。
MVC
历史
MVC模式于上世纪七八十年代,在Smalltalk编程语言中首次引入并逐步发展而来。MVC模式在现代桌面GUI应用、web应用等中得到了广泛的应用。
MVC 的关注点与重要性
从原始的 Smalltalk 的 MVC 实现中所发展出的理念就是分离展示,也就是 View层 和 Model层 应该被完全分离,这样带来了一个强烈的需求,那就是引入一个对象来辅助两者之间的通讯。
MVC 关注两个分离:从模型中分离表现 和 从试图中分离控制器
-
从模型中分离的表现:
- 从根本上讲表现层和模型的关注点不同,当开发试图时,工程师的关注点是 UI 框架,如何布局界面,当 Model 层开发时,工程师则关注业务逻辑策略,数据交互等
- 表现和视图的分离,有助于工程师使用同一套模型开发多个表现层
- 模型层与表现层分离,可以更容易测试业务逻辑,同时可以通过尝试 Mock 业务逻辑来测试表现层
-
分离控制器
- 控制器是视图的策略,使视图在不同场景触发不同策略
- 控制器监听 Model 状态变化而更新 View,View 层不在依赖 Model
传统 MVC
主要交互流程:
- 用户与 View 交互
- View 触发 Controller 处理特定事件策略(TouchUpInside、ViewDidAppear.....)
- Controller 触发 Model Action 更新 Model state ,或者更新 View
- Model 通知 View 发生变更
- View 获取 Model 数据并更新自身
- 传统 MVC 强调 Model 层不能依赖 View,而 View 是直接附着在 Model 层,并观察所有 model 变化的,Controller 存在的目的仅仅是捕捉用户事件,把事件转发给 model
Cocoa MVC
主要交互流程
- 用户与 View 交互
- View 触发 Controller 处理特定事件的策略(TouchUpInside、ViewDidAppear.....)
- Controller 触发 Model Action 更新 Model state,或者 COntroller 更新 View
- Controller 监听到 Model 变化
- Controller 更新 View
讨论
分层问题
Model : Model 指的是 Model 层,不是指单个类或者对象,不仅指数据传输对象,它包含 Model 对象、文档对象。
View:可以是一个 UIKit 对象,如 UIView,也可以是一个 Nib 文件、View 层一些枚举,结构体、模型。
Controller:一般指 UIViewController,AppDelegate 也是一种 Controller。
职责划分错乱
- Controller 包含 Model state 与 Model Action
Controller 是一组 View 的策略的集合,本身并不可复用,测试难度高。他做的事情是将 View Action 解释成 Model Action,并触发 Model 层状态改变,不宜直接在 Controller 做业务逻辑,这样做其实就是把 MVC 要从 View 分离的 M 层又耦合到了 C 层。
- Controller包含View层逻辑
常见的是我们会在ViewDidLoad(系统自动触发的View Action)中,拿到根视图开始布局。
这里可以参考苹果推荐的做法:每个Controller持有的两个View层对象:一个根式图(UIView),以及一个布局对象(Nib文件)
当我们希望使用代码的方式布局时,也可以创建这个View层的布局对象,使C层仅仅是View层的策略。则不会产生C层需要知道View层怎么展示的问题。
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
@property(null_resettable, nonatomic,strong) UIView *view;
@property(nullable, nonatomic, readonly, copy) NSString *nibName;
@property(nullable, nonatomic, readonly, strong) NSBundle *nibBundle;
@property(nullable, nonatomic, readonly, strong) UIStoryboard *storyboard
单向数据流
Controller不要在同一个方法中更新Model与View。
当所有的状态都在 model 层中被维护,而且所有的变更都通过完整的反馈回路路径进行传递时,则可以将它称为单向数据流。
即:任意的 view 对象或者中间层对象只能够通过 model 发出的通知来进行创建和更新。
当一个更改 model 的 view action 发生时,controller 不应该直接去操作 view 层级。正确的做法是,controller 去订阅 model 通知,并且在当通知到达时再更改 view 层级。这样一来,数据流就可以单向进行:view action 被转变为 model 变更,然后 model 发送通知,这个通知最后被转为 view 变更。view与model总是同步的。
Controller层臃肿问题
- 绝大部分Controller臃肿是从前边【职责划分错乱】所说问题开始的,核心原因是没有遵循MVC的规范。
- 除了未正确遵循职责划分规范导致的臃肿,C层臃肿的第二个原因是承担了过多的主要责任(接收View Action 翻译为Model Action,观察 model展示更新 view)之外的工作。这些controller 通常都有很多角色或职责。通常每一个角色或者职责都可以被提取为一个单独的对象。
- 同一个Controller包含明显不同的业务领域主要职责,可以使用子Controller进行拆分(例如ChildViewController,组件化)。
MVVM
历史
- Model-View-ViewModel (MVVM) 是一种基于 MVC 进行改进的模式。最早由微软在 Windows Presentation Foundation (WPF) 的项目中引入。它使用一种基于 XML描述性语言来描述 view 绑定某个 view-model 上的属性。
- 现在的ReactiveX 最初就是来源于微软,后同样的思想被移植到不同语言(如RxSwift)的。
- MVVM 是 目前iOS 上最流行的 MVC 的变形设计模式。在iOS中一般使用RxSwift等框架来实现绑定。
- 响应式编程主要思想是一切皆是流。是一种用来描述数据源和数据消费端之间数据流动的模式,它将这种流动描绘为一个变形管道。数据被看作一个在管道中流动的序列。数据管道的可观察量,数据变形以及订阅者三者分离。
主要交互流程
- 用户与 View 交互
- View 触发 Controller 处理特定事件的策略(TouchUpInside、ViewDidAppear…)
- Controller 触发 ViewModel Action
- ViewModel 触发 Model Action 更新 Model state
- ViewModel 监听到 Model 层变化,更改 ViewModel state
- ViewModel state的变更触发View展示内容变更
作为MVC的变体,有哪些变化
- 将所有 model 相关的任务 (更新 model、观察model变更、将 model state 转换为可以View显示的形式view state等) 从 controller 层抽离出来,放到新的view-model 层中。
- 通过把 model 相关的观察、model state到view state的数据变形逻辑,从 controller 层移除出去,减少了 controller 所需要承担的责任。
- ViewModel暴露的属性,为view state 提供了一套干净的接口,可以独立测试。
- 使用响应式编程来描述 view model 对象 view model state 与 View 显示值view state之间的关系。
- MVVM结合响应式编程,可更好的体现MVVM模式的优势。但响应式编程本身,有人对它趋之若鹜也有人对它避之不及。为了弱化响应式编程对了MVVM 模式的使用限制,出现了脱离响应式编程的MVVM方案。一般是使用观察者、通知、一些语法糖等替换响应式编程。
ViewModel所暴露的数据
- ViewModel 暴露的属性,应该是直接可以用于View展示的。Controller 内声明ViewModel 与View的绑定时,不应该再对ViewModel State再做进一步转换。
- MVVM在MVC上的改进便是在C与M之间增加了一个VM的中间层。并把原来C中监控M层的变化,转化为V层可现实的形式的这部分责任从C从抽离到了VM层。如果有了VM层,仍然把model status转View status的逻辑放在C层,其实还是在写MVC。
ViewModel耦合Model层逻辑
业务逻辑属于Model层。model status转View status(展示逻辑)属于VM层。
单向数据流
与之前mvc描述的内容类似,viewmodel 不要在同一个方法中同时触发 model action 并更新 viewModel state。正确的做法是,当一个更改 model 的 view action 发生时,viewModel 去订阅 model 通知,当通知到达时再更改 ViewModel state 。
常见误区:认为仅通过 VM 与 View 的绑定机制便实现了单向数据流。
总结
这些模式在干什么
如何将 app 分解为不同的接口和不同概念层次各个部分。不同部分的职责、依赖关系、通讯方式。控制流、数据流在不同部分之间的流动方向,传递方式等。
开发中该怎么选择
MVC及其变种天生适合应用在iOS开发中
- Apple 在所有的项目中都使用了MVC模式,加上 Cocoa 本身就是针对MVC模式设计的,所以 Cocoa MVC 成为了 iOS,macOS,tvOS 和 watchOS 上官方认证的 app 架构模式。最容易使用。
- MVC 的自由度允许我们在使用时引入非常多的变种。如:MVCS、MVVM、MVVMC。
其他从Web App中引入到iOS中的模式(Flux、Redux、VIPER 、ComponentKit)?
- 它们主要把 controller 的职责分散到三到四个不同的类中,并用严格的顺序将它们排列起来。在序列中的每个类都不允许直接引用序列中前面的类。为了强制单方向的引用这一规则,这些模式需要非常多的协议,类,以及在不同层中传递数据的方式。
- ComponentKit 使用类 DSL 的语法来进行声明式和可变形的 view 构建,和React较相似。
对于这些模式,我们更主要的是要学习它们的思想。完全引入至iOS开发中去整体替换 MVX 相关模式需慎重。