iOS之深入解析App的架构设计

一、概述
① 应用架构
  • App 架构是软件设计的一个分支,它关心的是如何设计一个 App 的结构。具体来说,它关注于两个方面:如何将 App 分解为不同的接口和概念层次部件,以及这些部件之间和自身的不同操作中 所使用的控制流和数据流路径。
  • 通常使用简单的框图来解释 App 的架构,比如,Apple 的 MVC 模式可以通过 model、 view 和 controller 三层结构来描述,如下所示:

在这里插入图片描述

  • 在一个 MVC 项目中,绝大部分的代码都会集中其中的某个层级上。但这种简单的框图几乎无法解释在实践中模式的操作方式,这是因为在实际的 App 架构中, 部件的构建有非常多的可能性。比如说:事件流在层中穿梭的方式是什么?部件之间是否应该在编译期间或者运行时持有对方?要怎么读取和修改不同部件中的数据?以及状态的变更应该以哪条路径在 App 中进行?
② Model 和 View
  • 在最高的层级上,App 架构其实就是一套分类,不同的部件会被归纳到某个类型中去,科技将这些不同的种类叫做层次。一个层次指的是遵循一些基本规则并负责特定 功能的接口和其他代码的集合。
  • Model 层是 App 的内容,它不依赖于 (像 UIKit 那样的) 任何 App 框架,也就是说,程序员对 model 层有完全的控制。Model 层通常包括 model 对象 (在录音 App 中的例子是文件夹和录音对象) 和协调对象 (比如 App 中的负责在磁盘上存储数据的 Store 类型)。被存储在磁盘上的那部分 model 称之为文档 model (documentation model)。
  • View 层是依赖于 App 框架的部分,它使 model 层可⻅,并允许用戶进行交互,从而将 model 层转变为一个 App。当创建 iOS 应用时,view 层几乎总是直接使用 UIKit。不过,也会看到在有些架构中,会使用 UIKit 的封装来实现不同的 view 层。另外,对一些其他的像是游戏类的自定义应用,view 层可以不是 UIKit 或者 AppKit,它可能是 SceneKit 或者 OpenGL 的某种封装。
  • 有时候,选择使用结构体或者枚举来表示 model 或者 view 的实例,而不使用类的对象。 在实践中,类型之间的区别非常重要,但是当在 model 层中谈到对象、结构体和枚举时, 会将三者统一地称为 model 对象。类似地,也会把 view 层的实例叫做 view 对象,实际上它们也可能是对象、结构体或者枚举。
  • View 对象通常会构成一个单一的 view 层级,在这个层级中,所有的 view 对象通过树结构的方 式连接起来。在树的根部是整个屏幕,屏幕中存在若干窗口,接下来在树的分支和叶子上是更多的小 view。类似地,view controller 也通常会形成 view controller 层级。不过,model 对象却不需要有明确的层级关系,在程序中它们可以是互不关联的独立 model。
  • 当提到 view 时,通常指的是像一个按钮或者一个文本 label 这样的单一 view 对象。当提到 model 时,通常指的也是像一个 Recording 实例或者 Folder 实例这样的单个 model 对象。在该话题的大多数文献中,model 在不同上下文中指的可能是不同的事情。它可以指代一个 model 层,model 层中的具体的若干对象,文档 model,或者是 model 层中不关联的文档。
  • 定义一个 model 层的最重要的理由是,它为程序提供一个表述事实的单一来源,这会让逻辑清晰,行为正确,我们的程序便不会被应用框架中的实现细节所支配。如果 model 层能做到和应用框架分离,就可以完全在 App 的范围之外使用它。我们可以很容易地在另外的测试套件中运行它,或者用一个完全不同的应用框架重写新的 view 层,那么这个 model 层将能够用于 Android,macOS 或者 Windows 版本的 App 中。
③ App 的本质是反馈回路
  • View 层和 model 层需要交流,因此两者之间需要存在连接。假设 view 层和 model 层是被清晰地分开,而且不存在无法解耦的联结的话,两者之间的通讯需要如下所示:

在这里插入图片描述

  • 从根本上说,用戶界面是一个同时负责展示和输入功能的反馈设备,所以毫无疑问,这导致的结果就是一个反馈回路,每个 App 设计模式所面临的挑战是如何处理上图中的箭头所包含的 交流,依赖和变换。
  • 在 model 层和 view 层之间不同的路径拥有不同的名称:
    • 用戶发起的事件会导致 view 的响应, 把由此引起的代码路径称为 view action,像是点击按钮或者选中 table view 中的某一行就属于 view action。
    • 当一个 view action 被送到 model 层时,它会被转变为 model action (或者说,让 model 对象执行一个 action 或者进行更新的命令),这种命令也被叫做一个消息 (特别 在当 model 是被 reducer 改变时)。
    • 将 view action 转变为 model action 的操作,以及路径上的其它逻辑被叫做交互逻辑。
  • 一个或者多个 model 对象上状态的改变叫做 model 变更。Model 的变更通常会触发一个 model 通知,比如说从 model 层发出一个可观测的通知,它描述 model 层中什么内容发生了改变。当 view 依赖于 model 数据时,通知会触发一个 view 变更,来更改 view 层中的内容,这些通知可以以多种形式存在:Foundation 中的 Notification、代理、回调、或者是其他机制, 都是可以的。将 model 通知和数据转变为 view 更改的操作,以及路径上的其他逻辑被叫做表现逻辑。
  • 根据 App 模式的不同,有些状态可能是在文档 model 之外进行维护的,这样一来,更新这些状 态的行为就不会追随文档 model 的路径。在很多模式中的导航状态就这种行为的一个常⻅例 子,在 view 层级中的某个部分 (或者按照 Cocoa Storyboard 中使用的术语,将它称为 scene) 可能会被换出或者换入层级中。
  • 在 App 中非文档 model 的状态被叫做 view state。在 Cocoa 里,大部分 view 对象都管理着它 们自己的 view state,controller 对象则管理剩余的 view state。在 Cocoa view state 的框图 中,通常会有加在反馈回路上的捷径,或者单个层自身进行循环。在有一些架构中,view state 不属于 controller 层,而是属于 model 层的部分 (不过,根据定义,view controller 并不是文 档 model 的一部分)。
  • 当所有的状态都在 model 层中被维护,而且所有的变更都通过完整的反馈回路路径进行传递时,就将它称为单向数据流。当任意的 view 对象或者中间层对象只能够通过 model 发出的通知来进行创建和更新 (换句话说,view 或者中间层不能通过捷径来更新自身或者其他的 view) 时,这个模式通常就是单向的。
④ 架构技术
  • Apple 平台的标准 Cocoa 框架提供了一些架构工具:
    • Notification 将值从单一源广播给若干个收听者;
    • 键值观察 (KVO) 可以将某个对象上属性的改变报告给另一个对象。
  • 使用到的第三方技术中可能包含了响应式编程,响应式编程也是一种用来交流变更的工具, 不过和通知或者 KVO 不同的是,它专注于在源和目标之间进行变形,让逻辑可以在部件之间传输信息的同时得以表达。
  • 可以使用像是响应式编程或者 KVO 这样的技术创建属性绑定。绑定接受一个源和一个目 标,无论何时,只要源发生了变化,目标也将被更新。这和手动进行观察在语法上有着不同, 不再需要写观察的逻辑,而只需要指定源和目标,接下来框架将会处理其余部分的 工作。
  • macOS 上的 Cocoa 包含有 Cocoa 绑定技术,它是一种双向绑定,所有的可观察对象同时也是 观察者,在一个方向上建立绑定连接,会在反方向也创建一个连接。不论是 RxCocoa,还是 CwlViews,都不是双向绑定的。
⑤ App 任务
  • 要让程序正常工作,view 必须依赖于 model 数据来生成和存在,配置 view,让它可以对model 进行更改,并且能在 model 更新时也得到更新。 所以,需要决定在 app 中如何执行下列任务:
    • 构建—谁负责构建 model 和 view,以及将两者连接起来?
    • 更新 model,如何处理 viewaction?
    • 改变 view,如何将 model 的数据应用到 view 上去?
    • viewstate 如何处理导航和其他一些 modelstate 以外的状态?
    • 测试为达到一定程度的测试覆盖,要采取怎样的测试策略?
二、常用的设计模式
① Model-View-Controller
  • 标准的 CocoaModel-View-Controller(MVC) 是 Apple 在示例项目中所采用的设计模式。它是 Cocoa App 中最为常⻅的架构,同时也是在 Cocoa 中讨论架构时所采用的基准线。
  • 在 Cocoa MVC 中,一部分 controller 对象负责处理 model 或者 view 层范畴之外的所有任务。这意味着,controller 层接收所有的 view action,处理所有的交互逻辑,发送所有的 model action,接收所有的 model 通知,对所有用来展示的数据进行准备,最后再将它们应用到 view 的变更上去。
  • 在上文中的 App 反馈回路的图,会发现在 model 和 view 之间的箭头上,几乎每个标签都是 controller,而且要知道,上图中的构建和导航任务并没有标注出来,它们也会由 controller 来处理。
  • 下面是 MVC 模式的框图,它展示了一个 MVC App 的主要通讯路径:

在这里插入图片描述

  • 图中的虚线部分代表运行时的引用,view 层和 model 层都不会直接在代码中引用 controller。 实线部分代表编译期间的引用,controller 实例知道自己所连接的 view 和 model 对象的接口。
  • 如果在这个图标外部描上边界的话,就得到了一个 MVC 版本的 App 反馈回路。注意:在图表中其他的路径并不参与整个反馈回路的路径 (也就是 view 层和 controller 层上那些指向自身的箭头)。
  • 构建
    • App 对象负责创建最顶层的 view controller,这个 view controller 将加载 view,并且知道应 该从 model 中获取哪些数据,然后把它们显示出来。
    • Controller 要么显式地创建和持有 model 层,要么通过一个延迟创建的 model 单例来获取 model。
    • 在多文档配置中,model 层由更低层 的像是 UIDocument 或 NSDocument 所拥有。那些和 view 相关的单个 model 对象,通常会 被 controller 所引用并缓存下来。
  • 更改 Model
    • 在 MVC 中,controller 主要通过 target/action 机制和 (由 storyboard 或者代码进行设置的) delegate 来接收 view 事件。
    • Controller 知道自己所连接的 view,但是 view 在编译期间却没有 关于 controller 接口的信息。
    • 当一个 view 事件到达时,controller 有能力改变自身的内部状态, 更改 model,或者直接改变 view 层级。
  • 更改 View
    • 在所理解的 MVC 中,当一个更改 model 的 view action 发生时,controller 不应该直接去 操作 view 层级。正确的做法是,controller 去订阅 model 通知,并且在当通知到达时再更改 view 层级。
    • 这样一来,数据流就可以单向进行:view action 被转变为 model 变更,然后 model 发送通知,这个通知最后被转为 view 变更。
  • View State
    • View state 可以按需要被 store 在 view 或者 controller 的属性中。相对于影响 model 的 view action,那些只影响 view 或 controller 状态的 action 则不需要通过 model 进行传递。
    • 对于 view state 的存储,可以结合使用 storyboard 和 UIStateRestoring 来进行实现,storyboard 负责记录活跃的 controller 层级,而 UIStateRestoring 负责从 controller 和 view 中读取数据。
  • 测试
    • 在 MVC 中,view controller 与 app 的其他部件紧密相连,边界的缺失使得为 controller 编写单元测试和接口测试十分困难,集成测试是余下的为数不多的可行测试手段之一。
    • 在集成测试中,构建相连接的 view、model 和 controller 层,然后操作 model 或者 view,来测试是 否能得到我们想要的结果。集成测试的书写非常复杂,而且它涵盖的范围太广了。它不仅仅测试逻辑,也测试部件是如何 连接的 (虽然在一些情况下和 UI 测试的⻆度并不相同)。
    • 不过,在 MVC 中通过集成测试,通常达到 80% 左右的测试覆盖率是有可能的。
  • MVC 的重要性:因为 Apple 在所有的实例项目中都使用了这种模式,加上 Cocoa 本身就是针对这种模式设计的,所以 Cocoa MVC 成为了 iOS、macOS、tvOS 和 watchOS 上官方认证的 App 架构模式。
② Model-View-ViewModel + 协调器
  • MVVM 和 MVC 类似,也是通过基于场景 (scene,view 层级中可能会在导航发生改变时切入或者换出的子树) 进行的架构。相较于 MVC,MVVM 在每个场景中使用 view-model 来描述场景中的表现逻辑和交互逻辑。
  • View-model 在编译期间不包含对 view 或者 controller 的引用,它暴露出一系列属性,用来描述每个 view 在显示时应有的值,把一系列变换运用到底层的 model 对象后,就能得到这些最终可以直接设置到 view 上的值。
  • 实际将这些值设置到 view 上的工作,则由预先建立的绑定来完成,绑定会保证当这些显示值发生变化时,把它设定到对应的 view 上去。响应式编程是用来表达这类声明和变换关系的很好的工具,所以它天生就适合 (虽说不是严格必要) 被用来处理 view-model。在很多时候,整个 view-model 都可以用响应式编程绑定的方式,以声明式的形式进行表达。
  • 在理论上,因为 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 的引用。
  • View-model 可以从 view controller 和 view 中独立出来,也可以被单独测试。同样,view controller 也不再拥有内部的 view state,这些状态也被移动到了 view-model 中。在 MVC 中 view controller 的双重⻆色 (既作为 view 层级的一部分,又负责协调 view 和 model 之间的交互),减少到了单一⻆色 (view controller 仅仅只是 view 层级的一部分)。
  • 协调器模式的加入进一步减少了 view controller 所负责的部分:现在它不需要关心如何展示其它的 view controller 了。因此,这实际上是以添加了一层 controller 接口为代价,降低了 view controller 之间的耦合。
  • 构建
    • 对于 model 的创建和 MVC 中的保持不变,通常它是一个顶层 controller 的职责。不过,单独的 model 对象现在属于 view-model,而不属于 view controller。初始的 view 层级的创建和 MVC 中的一样,通过 storyboard 或者代码来完成。
    • 和 MVC 不同的是,view controller 不再直接为每个 view 获取和准备数据,它会把这项工作交给 view-model。 View controller 在创建的时候会一并创建 view-model,并且将每个 view 绑定到 view-model 所暴露出的相应属性上去。
  • 更改 Model
    • 在 MVVM 中,view controller 接收 view 事件的方式和 MVC 中一样 (在 view 和 view controller 之间建立连接的方式也相同)。
    • 不过,当一个 view 事件到达时,view controller 不会去改变自身的内部状态、view state、或者是 model。相对地,它立即调用 view-model 上的方 法,再由 view-model 改变内部状态或者 model。
  • 更改 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 变化实际发生之后再通知相关的观察者。View State
      View state 要么存在于 view 自身之中,要么存在于 view-model 里。
    • 和 MVC 不同,view controller 中不存在任何 view state。View-model 中的 view state 的变更,会被 controller 观 察到,不过 controller 无法区分 model 的通知和 view state 变更的通知。当使用协调器时, view controller 层级将由协调器进行管理。
  • 测试
    • 因为 view-model 和 view 层与 controller 层是解耦合的,所以可以使用接口测试来测试 view-model,而不需要像 MVC 里那样使用集成测试。接口测试要比集成测试简单得多,因为 不需要为它们建立完整的组件层次结构。
    • 为了让接口测试尽可能覆盖更多的范围,view controller 应当尽可能简单,但是那些没有被移出 view controller 的部分仍然需要单独进行测试。在实现中,这部分内容包括与协调器的交互,以及初始时负责创建工作的代码。
  • MVVM 是 iOS 上最流行的 MVC 的非直接变形的 App 设计模式。换言之,它和 MVC 相比,并没有非常大的不同,两者都是围绕 view controller 场景构建的,而且所使用的机制也大都相同。
  • 最大的区别可能在于 view-model 中对响应式编程的使用,它被用来描述一系列的转换和依赖关系。通过使用响应式编程来清晰地描述 model 对象与显示值之间的关系,为我们从总体上 理解应用中的依赖关系提供了重要的指导。
  • iOS 中的协调器是一种很有用的模式,因为管理 view controller 层级是一件非常重要的事情。协调器在本质上并没有和 MVVM 绑定,它也能被使用在 MVC 或者其他模式上。
③ Model-View-Controller + ViewState
  • MVC+VS 是为标准的 MVC 带来单向数据流方式的一种尝试。在标准的 Cocoa MVC 中,view state 可以由两到三种不同的路径进行操作,MVC+VS 则试图避免这点,让 view state 的处理更加易于管理。在 MVC+VS 中,明确地在一个新的 model 对象中,对所有的 view state 进行定义和表达,把这个对象叫做 view state model。
  • 在 MVC+VS 中,不会忽略任何一次导航变更,列表选择,文本框编辑,开关变更,model 展示或者滚动位置变更 (或者其他任意的 view state 变化),我们将这些变更发送给 view state model。每个 view controller 负责监听 view state model,这样变更的通讯会非常直接,在表现或者交互逻辑部分,不从 view 中去读取 view state ,而是从 view state model 中去获取它们:

在这里插入图片描述

  • 结果所得到的图表和 MVC 类似,但 controller 的内部反馈回路的部分 (被用来更新 view state) 有所不同,现在它和 model 的回路类似,形成了一个独立的 view state 回路。
  • 构建:和传统的 MVC 一样,将文档 model 数据应用到 view 上的工作依然是 view controller 的责任, view controller 还会使用和订阅 view state 。因为 view state model 和文档 model 都需要观察,所以相比于典型的 MVC 来说,需要多得多的通过通知进行观察的函数。
  • 更改 Model:当 view action 发生时,view controller 去变更文档 model (这和 MVC 保持不变) 或者变更 model state,不会去直接改变 view 层级,所有的 view 变更都要通过文档 model 和 view state model 的通知来进行。
  • 更改 View:Controller 同时对文档 model 和 view state model 进行观察,并且只在变更发生的时候更新 view 层级。
  • View State:View State 被明确地从 view controller 中提取出来,处理的方法和 model 是一样的:controller 观察 view state model,并且对应地更改 view 层级。
  • 测试:
    • 在 MVC+VS 中,使用和 MVC 里类似的集成测试,但是测试本身会非常不同。所有的测试都从一个空的根 view controller 开始,然后通过设定文档 model 和 view state model,这个根 view controller 可以构建出整个 view 层级和 view controller 层级。MVC 的集成测试中最困 难的部分 (设定所有的部件) 在 MVC+VS 中可以被自动完成。
    • 要测试另一个 view state 时,我 们可以重新设置全局 view state,所有的 view controller 都会调整自身。
    • 一旦 view 层级被构建,可以编写两种测试:第一种测试负责检查 view 层级是不是按照期望被建立起来,第二种测试检查 view action 有没有正确地改变 view state。
  • MVC+VS 主要是用来对 view state 进行教学的工具,在一个非标准 MVC 的 App 中,添加一个 view state model,并且在每个 view controller 中 (在已经对 model 进行观察的基础上) 观察这些 view state model,提供了不少优点:任意的状态恢复 (这种恢复不依赖于 storyboard 或者 UIStateRestoration),完整的用戶界面日志,以及为了调试目的,在不同的 view state 间进行跳转的能力。
④ Model 适配器-View 绑定器 (MAVB)
  • MAVB 是一种以绑定为中心的实验模式,在这个模式中,有三个重要的概念:view 绑定器, model 适配器,以及绑定。
  • View 绑定器是 view (或者 view controller) 的封装类:它构建 view,并且为它暴露出一个绑定列表,一些绑定为 view 提供数据 (比如,一个标签的文本),另一些从 view 中发出事件 (比如, 按钮点击或者导航变更)。
  • 虽然 view 绑定器可以含有动态绑定,但是 view 绑定器本身是不可变的,这让 MAVB 也成为了一种声明式的模式:声明 view 绑定器和它们的 action,而不是随着时间去改变 view 绑定器。
  • Model 适配器是可变状态的封装,它是由所谓的 reducer 进行实现的。Model 适配器提供了一个 (用于发送事件的) 输入绑定,以及一个 (用于接收更新的) 输出绑定。
  • 在 MAVB 中,不会去直接创建 view,相对地,只会去创建 view 绑定器。同样地,也从 来不会去处理 model 适配器以外的可变状态。在 view 绑定器和 model 适配器之间的 (两个方 向上的) 变换,是通过 (使用标准的响应式编程技术) 来对绑定进行变形而完成的。
  • MAVB 移除了对 controller 层的需求,创建逻辑通过 view 绑定器来表达,变换逻辑通过绑定来表达,而状态变更则通过 model 适配器来表达。结果得到的框图如下:

在这里插入图片描述

  • 构建:
    • Model 适配器 (用来封装主 model ) 和 view state 适配器 (封装顶层的 view state) 通常是在 main.swift 文件中进行创建的,这早于任何的 view。
    • View 绑定器使用普通的函数进行构建,这些函数接受必要的 model 适配器作为参数。实际的Cocoa view 则由框架负责进行创建。
  • 更改 Model:
    • 当一个 view (或者 view controller) 可以发出 action 时,对应的 view 绑定允许指定一个 action 绑定,在这里,数据从 view 流向 action 绑定的输出端。
    • 典型情况下,输出端会与一个 model 适配器相连接,view 事件会通过绑定进行变形,成为 model 适配器可以理解的一条消息,这条消息随后被 model 适配器的 reducer 使用,并改变状态。
  • 更改 View:当 model 适配器的状态发生改变时,它会通过输出信号产生通知。在 view 绑定器中,可以将 model 适配器的输出信号进行变形,并将它绑定到一个 view 属性上去。这样一来,view 属性就会在一个通知被发送时自动进行变更。
  • View State:View state 被认为是 model 层的一部分。View state action 以及 view state 通知和 model action 以及 model 通知享有同样的路径。
  • 测试:
    • 在 MAVB 中,通过测试 view 绑定器来测试代码。由于 view 绑定器是一组绑定的列表,可以验证绑定包含所期望的条目,而且它们的配置正确无误,可以和使用绑定来测试初始构建以及发生变化时的情况。
    • 在 MAVB 中进行的测试,与在 MVVM 中的测试很相似。不过,在 MVVM 中,view controller 有可能会包含逻辑,这导致在 view-model 和 view 之间有可能会存在没有测试到的代码。而 MAVB 中不存在 view controller,绑定代码是 model 适配器和 view 绑定器之间的唯一的代码, 这样一来,保证完整的测试覆盖要简单得多。
⑤ Elm 架构 (TEA)
  • TEA 和 MVC 有着根本上的不同,在 TEA 中,model 和所有的 view state 被集成为一个单个状 态对象,所有 App 中的变化都通过向状态对象发送消息来发生,一个叫做 reducer 的状态更新 函数负责处理这些消息。
  • 在 TEA 中,每个状态的改变会生成一个新的虚拟 view 层级,它由轻量级的结构体组成,描述 了 view 层级应该看上去的形式。虚拟 view 层级能够使用纯函数的方式来写 view 部分的代码,虚拟 view 层级总是直接从状态进行计算,中间不会有任何副作用。当状态发生改变时,使用同样的函数重新计算 view 层级,而不是直接去改变 view 层级。
  • Driver 类型 (这是 TEA 框架中的一部分,它负责持有对 TEA 中其他层的引用) 将对虚拟 view 层级和 UIView 层级进行比较,并且对它进行必要的更改,让 view 和它们的虚拟版本相符合。这个 TEA 框架中的 driver (驱动) 部件是随着 App 的启动而被初始化的,它自身并不知道要对应哪个特定的 App。要在它的初始化方法中传入这些信息:包括 App 的初始状态,一个通过消息更新状态的函数,一个根据给定状态渲染虚拟 view 层级的函数,以及一个根据给定状态计算通知订阅的函数 (比如,可以订阅某个 model store 更改时所发出的通知)。
  • 从框架的使用者的视⻆来看,TEA 的关于更改部分的框图是这样的:

在这里插入图片描述

  • 如果追踪这张图表的上面两层,会发现在 view 和 model 之间存在在本文开头是就说过的反馈回路,这是一个从 view 到状态,然后再返回 view 的回路 (通过 TEA 框架进行协调)。
  • 上面的回路代表的是 TEA 中处理副作用的方式 (比如将数据写入磁盘中):当在状态更新方法中处理消息时,可以返回一个命令,这些命令会被 driver 所执行。在上面的例子中,最重要的命令是更改 store 中的内容,store 反过来又被 driver 所持有的订阅者监听,这些订阅者可以触发消息来改变状态,状态最终触发 view 的重新渲染作为响应。
  • 构建:状态在启动时被构建,并传递给运行时系统 (也就是 driver)。运行时系统拥有状态,store 是一个单例。初始的 view 层级和之后更新时的 view 层级是通过同样的路径构建的:通过当前的状态,计算 出虚拟 view 层级,运行时系统负责更新真实的 view 层级,让它与虚拟 view 层级相匹配。
  • 更改 Model:虚拟 view 拥有与它们所关联的消息,这些消息在一个 view 事件发生时会被发送。Driver 可以接收这些消息,并使用更新方法来改变状态。更新方法可以返回一个命令 (副作用),比如想在 store 中进行的改动,Driver 会截获该命令并执行它,TEA 让 view 不可能直接对状态或者 store 进行更改。
  • 更改 View:运行时系统负责这件事,改变 view 的唯一方式是改变状态。所以,初始化创建 view 层级和更新 view 层级之间没有区别。
  • View State:View state 是包含在整体的状态之中的,由于 view 是直接从状态中计算出来的,导航和交互状态也同样会被自动更新。
  • 测试:
    • 在大多数架构中,让测试部件彼此相连往往要花费大量努力。在 TEA 中,不需要对此进行 测试,因为 driver 会自动处理这部分内容。类似地,不需要测试当状态变化时 view 会正确 随之变化,所需要测试的仅仅是对于给定的状态,虚拟 view 层级可以被正确计算。
    • 要测试状态的变更,可以创建一个给定的状态,然后使用 update 方法和对应的消息来改 变状态,然后通过对比之前和之后的状态,就可以验证 update 是否对给定的状态和消息返回了所期望的结果。
    • 在 TEA 中,还可以测试对应给定状态的订阅是不是正确。和 view 层级一样,update 函数和订阅也都是纯函数。
    • 因为所有的部件 (计算虚拟 view 层级,更新函数和订阅) 都是纯函数,可以对它们进行完全隔离的测试。任何框架部件的初始化都是不需要的,只用将参数传递进去,然后验证结果就行。
三、其他架构模式
① Model-View-Presenter
  • Model-View-Presenter (MVP) 是一种在 Android 上很流行的模式,在 iOS 中,也有相应的实现。在总体结构和使用的技术上,它粗略来说是一种位于标准 MVC 和 MVVM 之间的模式。
  • MVP 使用单独的 presenter 对象,它和 MVVM 中 view-model 所扮演的⻆色一样。相对 view-model 而言,presenter 去除了响应式编程的部分,而是把要展示的值暴露为接口上的属 性。不过,每当这些值需要变更的时候,presenter 会立即将它们推送到下面的 view 中去 (view 将自己作为协议暴露给 presenter)。
  • 从抽象的观点来看,MVP 和 MVC 很像。Cocoa 的 MVC,除了名字以外,就是一个 MVP, 它是从上世纪九十年代 Taligent 的原始的 MVP 实现中派生出来的 View,状态和关联的逻辑在两个模式中都是一样的。不同之处在于,现代的 MVP 中有一个分离的 presenter 实体,它使用协议来在 presenter 和 view controller 之间进行界定,Cocoa 的 MVC 让 controller 能够直接引用 view,而 MVP 中的 presenter 只能知道 view 的协议。
  • 有些开发者认为协议的分离对于测试是必要的。当在讨论测试时,会看到标准的 MVC 在没有任何分离的情况下,也可以被完整测试,所以感觉 MVP 并没有太大不同。如果对测试一个完全解耦的展示层有强烈需求的话,认为 MVVM 的方式更简单一些,让 view controller 通过观察去从 view-model 中拉取值,而不是让 presenter 将值推送到一个协议中去。
② VIPER,Riblets,和其他 “Clean” 架构
  • VIPER,Riblets 和其他类似的模式尝试将 Robert Martin 的 “Clean Architecture” 从 web app 带到 iOS 开发中,它们主要把 controller 的职责分散到三到四个不同的类中,并用严格的顺序将它们排列起来,在序列中的每个类都不允许直接引用序列中前面的类。
  • 为了强制单方向的引用这一规则,这些模式需要非常多的协议和类,以及在不同层中传递数据的方式。由于这个原因,很多使用这些模式的开发者会去使用代码生成器。我们的感觉是,这些代码生成器,以及任何的繁杂到需要生成器的模式,都产生了一些误导,将 “Clean” 架构带 到 Cocoa 的尝试通常都宣称它们可以管理 view controller 的 “肥大化” 问题,但这么做往往让代码库变得更大。
  • 虽然将接口分解是控制代码尺寸的一种有效手段,但是我们认为这应该按需进行,而不是教条式地对每个 view controller 都这么操作。分解接口需要我们对数据以及所涉及到的任务有清楚 的认识,只有这样,我们才能达到最优的抽象,并在最大程度上降低代码的复杂度。
③ 基于组件的架构 (React Native)
  • 如果选择使用 JavaScript 而不是 Swift 编程,或者 App 重度依赖于 web API 的交互, JavaScript 会是更好的选择,这时可能会考虑 React Native。
  • 如果想要找一些类似 React Native,但是是基于 Swift 的东西的话,可以看看对 TEA 的探索。MAVB 的实现也从 ComponentKit 中获得了一些启发,而 ComponentKit 本身又从 React 中获取灵感:它使用类 DSL 的语法来进行声明式和可变形的 view 构建,这和 React 中 Component 的 render 方法及其相似。
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值