iOS MVC、MVVM、MVP架构模式浅浅析

声明:本文很多部分是对王巍App 架构一书的学习笔记,如有侵权,请告知

我们需要决定在 app 中如何执行下列任务:

构建 — 谁负责构建 model 和 view,以及将两者连接起来?
更新 model — 如何处理 view action?
改变 view — 如何将 model 的数据应用到 view 上去?
view state — 如何处理导航和其他一些 model state 以外的状态?

App 的本质是反馈回路

“View 层和 model 层需要交流。所以,两者之间需要存在连接。假设 view 层和 model 层是被清晰地分开,而且不存在无法解耦的联结的话,两者之间的通讯就需要一些形式的翻译:”


V和M交互逻辑.png

“当一个 view action 被送到 model 层时,它会被转变为model action (或者说,让 model 对象执行一个 action 或者进行更新的命令)。这种命令也被叫做一个消息 (特别在当 model 是被 reducer 改变时,我们会这么称呼它)。将 view action 转变为 model action 的操作,以及路径上的其他逻辑被叫做交互逻辑。”

“当 view 依赖于 model 数据时,通知会触发一个 view 变更,来更改 view 层中的内容。这些通知可以以多种形式存在:Foundation 中的 Notification,代理,回调,或者是其他机制,都是可以的。将 model 通知和数据转变为 view 更改的操作,以及路径上的其他逻辑被叫做表现逻辑。”

MVC

“MVC 的核心思想是,controller 层负责将 model 层和 view 层撮合到一起工作。Controller 对另外两层进行构建和配置,并对 model 对象和 view 对象之间的双向通讯进行协调。所以,在一个 MVC app 中,controller 层是作为核心来参与形成 app 的反馈回路的:”

“图中的虚线部分代表运行时的引用,view 层和 model 层都不会直接在代码中引用 controller。实线部分代表编译期间的引用,controller 实例知道自己所连接的 view 和 model 对象的接口”

MVC.png

MVC 中有两个最常见的问题

  • 观察者模式失效
    第一个问题是,model 和 view 的同步可能失效。当围绕 model 的观察者模式没有被完美执行时,这个问题就会发生。常见的错误是,在构建 view 时读取了 model 的值,而没有对后续的通知进行订阅。另一个常见错误是在变更 model 的同时去更改 view 层级,这种做法假设了变更的结果,而没有等待 model 进行通知,如果 model 拒绝了这个变更的话,就会发生错误。这类错误会使得 view 和 model 不同步,奇怪的行为也随之而来。
  • 肥大的 View Controller
    View controller 需要负责处理 view 层 (设置 view 属性,展示 view 等),但是它同时也负责 controller 层的任务 (观察 model 以及更新 view),最后,它还要负责 model 层 (获取数据,对其变形或者处理)。结合它在架构中的中心角色,这使得我们很容易在不经意间把所有的职责都赋予 view controller,从而迅速让程序变得难以管理。

MVC构建

  1. 构建
    App 对象负责创建最顶层的 view controller,这个 view controller 将加载 view,并且知道应该从 model 中获取哪些数据,然后把它们显示出来。Controller 要么显式地创建和持有 model 层,要么通过一个延迟创建的 model 单例来获取 model。在多文档配置中,model 层由更低层的像是 UIDocument 或 NSDocument 所拥有。那些和 view 相关的单个 model 对象,通常会被 controller 所引用并缓存下来。
  2. 更改 Model
    在 MVC 中,controller 主要通过 target/action 机制和 (由 storyboard 或者代码进行设置的) delegate 来接收 view 事件。Controller 知道自己所连接的 view,但是 view 在编译期间却没有关于 controller 接口的信息。当一个 view 事件到达时,controller 有能力改变自身的内部状态,更改 model,或者直接改变 view 层级。
  3. 更改 View”
    “在我们所理解的 MVC 中,当一个更改 model 的 view action 发生时,controller 不应该直接去操作 view 层级。正确的做法是,controller 去订阅 model 通知,并且在当通知到达时再更改 view 层级。这样一来,数据流就可以单向进行:view action 被转变为 model 变更,然后 model 发送通知,这个通知最后被转为 view 变更。”
  4. View State
    View state 可以按需要被 store 在 view 或者 controller 的属性中。相对于影响 model 的 view action,那些只影响 view 或 controller 状态的 action 则不需要通过 model 进行传递。对于 view state 的存储,可以结合使用 storyboard 和 UIStateRestoring 来进行实现,storyboard 负责记录活跃的 controller 层级,“而 UIStateRestoring 负责从 controller 和 view 中读取数据。”

MVVM

“MVVM 构建的方式和 MVC 的模式很相似:controller 层充分了解程序的结构,它使用这些认知来对所有部件进行构建和连接。
MVVM 与 MVC 最大的区别可能在于 view-model 中对响应式编程的使用了,它被用来描述一系列的转换和依赖关系。通过使用响应式编程来清晰地描述 model 对象与显示值之间的关系,为我们从总体上理解应用中的依赖关系提供了重要的指导。
具体主要有三个不同:

  1. 必须创建 view-model。
  2. 必须建立起 view-model 和 view 之间的绑定。
  3. Model 由 view-model 拥有,而不是由 controller 所拥有。”

“MVVM 通常要求 controller 必须非常简单 (甚至简单到无需考虑)。另外,controller 必须尽可能地使用库提供的绑定方法。在这样的规则的保证下,理想情况中我们就不需要测试 controller 了,因为它没有包含我们自己的任何逻辑。究竟应该在 controller 中留存多少逻辑,根本上来说是掌握在程序员手上的。”

“MVVM 通过将 model 观察的代码以及其他显示和交互逻辑移动到围绕着数据流构建的隔离的类中,解决了 MVC controller 里不规则的状态交互所带来的有关问题。因为这是 MVC 中最显著的问题,而且会随着 Cocoa controller 的增大而恶化,这个变化在很大程度上缓解了 MVC 中 controller 肥大的问题。但是还有其他一些因素会使得 controller (以及 view-model) 变大,所以为了可持续发展,重构依然还是有需要的”

MVVM.png

MVVM的构建

  1. 构建
    和 MVC 不同的是,view controller 不再直接为每个 view 获取和准备数据,它会把这项工作交给 view-model。View controller 在创建的时候会一并创建 view-model,并且将每个 view 绑定到 view-model 所暴露出的相应属性上去。
  2. 更改 Model
    在 MVVM 中,view controller 接收 view 事件的方式和 MVC 中一样 (在 view 和 view controller 之间建立连接的方式也相同)。不过,当一个 view 事件到达时,view controller 不会去改变自身的内部状态、view state、或者是 model。相对地,它立即调用 view-model 上的方法,再由 view-model 改变内部状态或者 model。
  3. 更改 View
    和 MVC 不同,view controller 不监听 model。View-model 将负责观察 model,并将 model 的通知转变为 view controller 可以理解的形式。View controller 订阅 view-model 的变更,这通常通过一个响应式编程框架来完成,但也可以使用任意其他的观察机制。当一个 view-model 事件来到时,由 view controller 去更改 view 层级。
    为了实现单向数据流,view-model 总是应该将变更 model 的 view action 发送给 model,并且仅仅在 model 变化实际发生之后再通知相关的观察者。
  4. View State
    View state 要么存在于 view 自身之中,要么存在于 view-model 里。和 MVC 不同,view controller 中不存在任何 view state。View-model 中的 view state 的变更,会被 controller 观察到,不过 controller 无法区分 model 的通知和 view state 变更的通知。当使用协调器时,view controller 层级将由协调器进行管理。
  5. 测试
    因为 view-model 和 view 层与 controller 层是解耦合的,所以可以使用接口测试来测试 view-model,而不需要像 MVC 里那样使用集成测试。接口测试要比集成测试简单得多,因为不需要为它们建立完整的组件层次结构。
    为了让接口测试尽可能覆盖更多的范围,view controller 应当尽可能简单,但是那些没有被移出 view controller 的部分仍然需要单独进行测试。在我们的实现中,这部分内容包括与协调器的交互,以及初始时负责创建工作的代码。”

关于view-model

  • 实现状态恢复数据
    在状态恢复的方法上,MVVM 中为各个 controller 所存储的数据来源于 view-model,而非像 MVC 那样来源于 controller 本身,除此之外,两者所使用的策略大抵相同。

  • View-model 虽然名字里既有 view 又有 model,但是它所扮演的其实是不折不扣的类似 controller 的角色。

  • View-model 从 view controller 和 view 中独立出来,也可以被单独测试。同样,view controller 也不再拥有内部的 view state,这些状态也被移动到了 view-model 中。在 MVC 中 view controller 的双重角色 (既作为 view 层级的一部分,又负责协调 view 和 model 之间的交互),减少到了单一角色 (view controller 仅仅只是 view 层级的一部分)。

  • “View-model 在编译期间不包含对 view 或者 controller 的引用。它暴露出一系列属性,用来描述每个 view 在显示时应有的值。把一系列变换运用到底层的 model 对象后,就能得到这些最终可以直接设置到 view 上的值。实际将这些值设置到 view 上的工作,则由预先建立的绑定来完成,绑定会保证当这些显示值发生变化时,把它设定到对应的 view 上去。响应式编程是用来表达这类声明和变换关系的很好的工具,所以它天生就适合 (虽说不是严格必要) 被用来处理 view-model。在很多时候,整个 view-model 都可以用响应式编程绑定的方式,以声明式的形式进行表达。

  • “在理论上,因为 view-model 不包含对 view 层的引用,所以它是独立于 app 框架的,这让对于 view-model 的测试也可以独立于 app 框架。”

MVVM-C

“在理论上,因为 view-model 不包含对 view 层的引用,所以它是独立于 app 框架的,这让对于 view-model 的测试也可以独立于 app 框架。

由于 view-model 是和场景耦合的,我们还需要一个能够在场景间切换时提供逻辑的对象。在 MVVM-C 中,这个对象叫做协调器 (coordinator)。协调器持有对 model 层的引用,并且了解 view controller 树的结构,这样,它能够为每个场景的 view-model 提供所需要的 model 对象。”

和 MVC 不同,MVVM-C 中的 view controller 从来都不会直接引用其他的 view controller (所以,也不会引用其他的 view-model)。View controller 通过 delegate 的机制,将 view action 的信息告诉协调器。协调器据此显示新的 view controller 并设置它们的 model 数据。换句话说,view controller 的层级是由协调器进行管理的,而不是由 view controller 来决定的。如果我们忽略掉协调器,那么这张图表就很像 MVC 了,只不过在 view controller 和 model 之间加入了一个阶段。MVVM 将之前在 view controller 中的大部分工作转移到了 view-model 中,但是要注意,view-model 并不会在编译时拥有对 view controller 的引用。

“初步印象来说,因为 MVVM-C 加入了额外的一层来进行管理,看起来是比 Cocoa MVC 模式更加复杂。不过,在实现的层级,如果你能够始终如一地贯彻这个模式,代码会变得更简单一些。啊,这里说的简单并不意味着容易,只有当你对常见的响应式代码变形熟悉以后,才不会对书写代码感到无从下手,才不会对调试问题感到懊恼沮丧。不过,从令人高兴的一面来说,精心设计的数据管道通常不容易产生错误,在长期来看维护也更容易一些。”

MVVM-C.png

“iOS 中的协调器是一种很有用的模式,因为管理 view controller 层级是一件非常重要的事情。协调器在本质上并没有和 MVVM 绑定,它也能被使用在 MVC 或者其他模式上。”

协调器为每个 controller 的 view-model 设置初始的 model 对象。
View-model 将设定值和其他 model 数据及观察量进行合并。
View-model 将数据变形为 view 所需要的精确的格式。
Controller 将准备好的值绑定到各个 view 上去。

MVP

对于Controller层过于臃肿的问题,MVP模式则能较好地解决这个问题——既然UIViewController和UIView是耦合的,索性把这两者都归为View层,业务逻辑则独立存在于Presenter层,Model层保持不变。下图比较清除得展示了MVP模式的结构

屏幕快照 2018-10-04 00.09.48.png

MVC-VS

MVC+VS.png

MAVB

“model 适配器 - view 绑定器 (ModelAdapter-ViewBinder, MAVB)”

MAVB.png

其他模式

Elm 架构 (TEA)、VIPER、Riblets

响应式编程

“响应式编程是一种用来交流变更的工具,不过和通知或者 KVO 不同的是,它专注于在源和目标之间进行变形,让逻辑可以在部件之间传输信息的同时得以表达。”
“响应式编程是一种用来描述数据源和数据消费端之间数据流动的模式”
“数据变形的部分是响应式编程所能带来的最大优势,但同时它也是学习曲线最为陡峭的部分。”

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

推荐阅读更多精彩内容