美文网首页iOS开发之常用技术点
iOS APP 架构本质探讨(二)

iOS APP 架构本质探讨(二)

作者: Easyhoony | 来源:发表于2018-11-09 17:21 被阅读14次

设计模式总述

标准的 Cocoa Model-View-Controller (MVC) 是 Apple 官方在示例项目中所采用的设计模式, 它是 Cocoa app 中最为常见的架构,同时也是在 Cocoa 中讨论架构时所采用的基准线。

Model-View-ViewModel+ Coordinator(协调器) (MVVM-C) 是 MVC 的变种,它拥有单独的 "View-Model" (视图模型) 和一个用来管理 view controller 的协调器。MVVM 使用数据绑定 (通常会和响应式编程一起使用) 来建立 view-model 层和 view 层之间的连接。

我们将讨论的另外三种模式在 Cocoa 中并不常见,是还处于实验阶段的架构。这些架构为构建 app 提供了有用的见解,即使我们不选择将整个架构应用到我们的项目中,它们也对改进代码会有所帮助;

Model-View-Controller+ViewState (MVC+VS) 这种模式将所有的 view state 集中到一个地方,而不是让它们散落在 view 和 view controller 中。这和 model 层所遵循的规则相同,
Model 适配器-View 绑定器 (Model Adapter-ViewBinder, MAVB) 是本教程的一位大神所使用的实验性质的架构。MAVB 专注于构建声明式的 view,并且抛弃 Controller,采用绑定的方式来在 Model 和 View 之间进行通讯。
Elm 架构 (TEA) 与 MVC 或者 MVVM 这样的常见架构完全背道而驰。它使用虚拟 view 层级来构建 view,并使用 reducer 来在 model 和 view 之间进行交互。
我们会对每种模式中蕴含的哲学和选择进行概览性的介绍,在后面的章节里,我们会看到这些选择是如何对录音 app 的实现产生影响的。
当然,除了这五种模式以外,iOS 的 app 设计模式还有非常多的选择。在讨论每种模式的时候,我们会为你揭示我们选择这种模式进行介绍的原因。在本章的最后,我们也会对没有被我们在本书中介绍的一些其他模式进行简单讨论。

View-Comtroller-Model

在 Cocoa MVC 中,一小部分 Controller 对象负责处理 Model 或者 View 层范畴之外的所有任务。
这意味着,controller 层接收所有的 viewAction,处理所有的交互逻辑,发送所有的 modelAction,接收所有的 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 架构模式。

本书在录音 app 中所提供的这个专门的实现里,所使用的是经典的 MVC 方式,它是我们所认为的贯穿 iOS 和 macOS 历史中,最能精确反应共通特点的方式。然后,我们会看到,MVC 的自由度允许我们在使用时引入非常多的变种:很多来源于其他模式的思想,它们都可以被集成到整个 app 中的某个小部分中去。

  • 历史

MVC 这个名字第一次被提出是在 1979 年,Trygve Reenskaug 用它来描述 Smalltalk-76 上已经存在的 "template pattern" 应用。在他和 Adele Goldberg 讨论了术语方面的问题后,MVC 的名字被确定下来 (之前的名字包括 Model-View-Editor 和 Model-View-Tool-Editor 等)。

在原本的构想中,view 是直接“附着”在 model 层上,并观察所有 model 变化的。Controller 存在的目的仅仅是捕捉用户事件,并把它们转发给 model。这两个特性都是 Smalltalk 运行方式的产物,它们并不是为了现代的 app 框架所设计的,所以今天这种 MVC 的原始构想已经几乎绝迹了。

Cocoa 中的 MVC 实现可以追溯到大约 1997 年的 NeXTSTEP 4 的年代。在那之前,所有现在 controller 所担当的角色,通常都由一个 (像是 NSWindow 那样的) 高层 view 类来扮演。之后,从原始的 Smalltalk 的 MVC 实现中所发展出的理念是分离展示部分,也就是 view 层和 model 层应该被完全隔离开,这带来了一个强烈的需求,那就是要引入一个支持对象来辅助两者之间的通讯。NeXTSTEP 中 controller 的概念和 Taligent 稍早的 Model-View-Presenter 中的 presenter (展示器) 很相似。不过,在现在 Model-View-Presenter 这个名字通常被用来指代那些通过协议从 Controller 中将 view 抽象出来的类似 MVC 的模式。

Model-View-ViewModel+ Coordinator(协调器)

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 框架。


131541754998_.pic.jpg

由于 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 来决定的。
这些特性所形成的架构的总体结构如下图所示:


131541754998_.pic.jpg

如果我们忽略掉协调器,那么这张图表就很像 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 之间的耦合。

译者注:View-model 虽然名字里既有 view 又有 model,但是它所扮演的其实是不折不扣的类似 controller 的角色。在本书中,当上下文不明确时,我们将严格区分 view controller 和 controller。要注意,前者只是后者的一个子集,view 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 的重要性
    MVVM 是 iOS 上最流行的 MVC 的非直接变形的 app 设计模式。换言之,它和 MVC 相比,并没有非常大的不同;两者都是围绕 view controller 场景构建的,而且所使用的机制也大都相同。

最大的区别可能在于 view-model 中对响应式编程的使用了,它被用来描述一系列的转换和依赖关系。通过使用响应式编程来清晰地描述 model 对象与显示值之间的关系,为我们从总体上理解应用中的依赖关系提供了重要的指导。
iOS 中的协调器是一种很有用的模式,因为管理 view controller 层级是一件非常重要的事情。协调器在本质上并没有和 MVVM 绑定,它也能被使用在 MVC 或者其他模式上。

  • 历史
    MVVM 由 Ken Cooper 和 Ted Peters 提出,他们当时在微软工作,负责后来变成 Windows Presentation Foundation (WPF) 的项目,这是微软 .NET 的 app 框架,并于 2005 年正式发布。
    WPF 使用一种基于 XML,被称为 XAML 的描述性语言来描述 view 所绑定的某个 view-model 上的属性。在 Cocoa 中,没有 XAML,我们必须使用像是 RxSwift 这样的框架和一些 (通常存在于 controller 中的) 代码来完成 view-model 和 view 的绑定。

译者注:微软在 MVVM 和响应式编程的领域深耕已久,这也是为什么 RxSwift 或者说 Rx 系列的响应式编程框架如此流行的原因 (整个 ReactiveX 最初就是来源于微软的 Reactive Extensions,然后再将同样的思想移植到不同语言的)。
MVVM 和我们在 MVC 历史中提到的 MVP 模式非常类似.不过,在 Cooper 和 Peters 的论述中,MVVM 中 view 和 view-model 的绑定需要明确的框架支持,但 presenter 是通过传统的手动方式来传递变化。

iOS 中的协调器则是最近才 (重新) 流行起来的, Soroush Khanlou 在 2015 年时在他的网站上描述了这个想法。协调器基于 app controller 这样的更古老的模式,而它们在 Cocoa 和其他平台上已经存在了有数十年之久。

相关文章

  • iOS APP 架构本质探讨(二)

    设计模式总述 标准的 Cocoa Model-View-Controller (MVC) 是 Apple 官方在示...

  • iOS APP 架构本质探讨(一)

    APP 架构是APP设计的一个 重要组成部分,就像框架结构的楼房一样, 决定了你 APP 随着版本迭代、业务量增加...

  • 架构之路

    1. 架构之路 (一) —— iOS原生系统架构(一)2. 架构之路 (二) —— APP架构分析(一)3. 架构...

  • 架构

    唯一可行的 iOS 架构 iOS App 的最佳架构,存在吗?

  • 学习笔记-Android Native APP测试

    1. Android app架构 1.1 Native App 即原生APP开发模式,利用iOS、Android开...

  • 学习笔记 - iOS 组件化方案

    一、蘑菇街 App 的组件化之路 二、iOS应用架构谈 组件化方案 三、蘑菇街 App 的组件化之路·续 四、iO...

  • iOS APP架构

    作者:小萝卜链接:https://zhuanlan.zhihu.com/p/37360635来源:知乎著作权归作者...

  • 程序员该了解的架构细节 —— MVC&MVVM

    APP 架构模式基础 App 的本质是反馈回路 GUI(图形用户界面 Graphical User Interfa...

  • iOS网络层架构探讨

    作者大神Q原创,谢绝转载,谢谢,demo地址 NetworkArchitecture。 下面关于网络的封装的设计还...

  • ios 架构之谈

    分类: IOS(26) 目录(?)[+] 在开始谈app架构之前,曾经我一度认为,一个好的app就是需要有好的架构...

网友评论

    本文标题:iOS APP 架构本质探讨(二)

    本文链接:https://www.haomeiwen.com/subject/bcdoxqtx.html