iOS 中的模型-视图-控制器 (MVC)

934 阅读10分钟

什么是 MVC

随着项目的方便,是时候开始学习模型、视图和控制器了。但首先,MVC 到底是什么?

注意:如果您已经了解 MVC 的概念,请随时跳到下一部分,您将开始了解最佳实践。

MVC 是一种软件开发模式,由三个主要对象组成:

  • 模型是您的数据所在的位置。诸如持久性、模型对象、解析器、管理器和网络代码之类的东西都在那里。
  • View层是应用程序的表面。它的类通常是可重用的,因为它们不包含任何特定于域的逻辑。例如,aUILabel是在屏幕上呈现文本的视图,它是可重用和可扩展的。
  • 控制器通过委托模式在视图和模型之间进行调解。在理想情况下,控制器实体不会知道它正在处理的具体视图。相反,它将通过协议与抽象进行通信。一个典型的例子是UITableView通过协议与其数据源通信的方式UITableViewDataSource

当你把所有东西放在一起时,它看起来像这样:

01.png

这些对象中的每一个都意味着彼此分开,并且每个都履行特定的角色。在构建使用特定模式的应用程序时,在本例中为 MVC,目标是在构建应用程序的所有层时遵循该模式。

Apple 的 MVC 文档详细解释了这些层,可以给你一个很好的理论理解。但是,从实际的角度来看,它有点不足。

在本教程中,您将学习将 MVC 视为一种模式,而不是绝对不能违反的严格规则。就像软件开发中的许多事情一样,没有什么是完美的,MVC 也不是。您会遇到灰色区域,但您做出的任何决定都不是错误的决定。只要您没有太大的文件或难以扩展的代码,那么您可能会做得很好。使用 MVC——以及其他相关的模式——作为你的应用程序的架构指南和基础。

话虽如此,是时候更详细地查看每一层了。

02.png

型号 (M)

模型层包含应用程序的数据。这并不奇怪,但您的项目中通常还有其他类和对象可以包含在这一层中:

  • 网络代码:您最好只使用一个类在整个应用程序中进行网络通信。它可以轻松抽象出所有网络请求共有的概念,例如 HTTP 标头、响应和错误处理等。
  • 持久化代码:当将数据持久化到数据库、Core Data 或在设备上存储数据时使用它。
  • 解析代码:您应该在模型层中包含解析网络响应的对象。例如,在 Swift 模型对象中,您可以使用 JSON 编码/解码来处理解析。这样,一切都是自包含的,您的网络层不必知道所有模型对象的详细信息即可解析它们。业务和解析逻辑在模型中都是自包含的。
  • 管理器和抽象层/类:每个人都使用它们,每个人都需要它们,没有人知道如何称呼它们或它们属于哪里。具有通常充当其他类之间的粘合剂的典型“管理器”对象是正常的。这些也可以是更低级别、更健壮的 API 的包装器:iOS 上的钥匙串包装器、帮助通知的类或帮助您使用 HealthKit 的模型。
  • 数据源和委托:可能不太常见的是开发人员依赖模型对象作为其他组件(如表或集合视图)的数据源或委托。即使有很多最好保持分隔的自定义逻辑,在控制器层中实现这些也是很常见的。
  • 常量:最好有一个带有常量的文件。考虑将它们放在结构或变量的结构中。这样,您可以重用情节提要名称、日期格式化程序、颜色等。
  • 助手和扩展:在你的项目中,你经常会添加方法来扩展字符串、集合等的能力。这些也可以被认为是模型的一部分。

您可以包含更多类和对象,但这些似乎是最常见的。同样,目标不是过分关注什么是或不是模型的语义。相反,它是有一个坚实的基础来完成你的工作。

从测试的角度来看,该模型是一个很好的候选者。您可以确认业务逻辑、模拟您的一些网络或持久性方法或围绕模型层的敏感部分添加测试。

观点(五)

当用户与您的应用程序交互时,他们正在与视图层进行交互。它不应包含任何业务逻辑。在代码方面,您通常会看到:

  • UIView子类:这些范围从基本UIView到复杂的自定义 UI 控件。
  • 属于 UIKit/AppKit 的类。
  • 核心动画。
  • 核心图形。

在这一层中发现的典型代码异味以不同的方式表现出来,但归结为在视图层中包含与 UI 无关的任何内容。这可以包括网络调用、业务逻辑、操作模型等等。

检查视图层时,请使用以下清单:

  • 它与模型层交互吗?
  • 它是否包含任何业务逻辑?
  • 它会尝试做任何与 UI 无关的事情吗?

如果您对这些问题中的任何一个回答“是”,您就有机会进行清理和重构。

当然,这些规则不是一成不变的,有时你需要改变它们。尽管如此,记住它们很重要。

编写良好的视图组件通常是可重用的,但不要从一开始就专注于此。很容易陷入考虑使用您构建的酷按钮并使其与您的应用程序可能永远不需要的十几种不同场景兼容的想法。

考虑仅在您真正需要时使组件可重用。当您有多个用例时,是时候让组件更通用了。

从测试的角度来看,iOS 中的 UI 测试允许您测试转换、具有特定属性的 UI 元素或按预期工作的 UI 交互等内容。

控制器 (C)

控制器层是应用程序中可重用性最低的部分,因为它通常涉及特定于域的规则。毫不奇怪,在你的应用中有意义的东西在别人的应用中并不总是有意义。然后控制器将使用模型层中的所有元素来定义应用程序中的信息流。

通常,您会看到该层中的类决定以下内容:

  • 我应该首先访问什么:持久性还是网络?
  • 我应该多久刷新一次应用程序?
  • 下一个屏幕应该是什么以及在什么情况下?
  • 如果应用程序进入后台,我应该清理什么?
  • 用户点击了一个单元格;接下来我该怎么办?

将控制器层视为应用程序的大脑或引擎;它决定接下来会发生什么。

您可能不会在控制器层进行太多测试,因为在大多数情况下,它的目的是启动、触发数据加载、处理 UI 交互、在 UI 和模型之间进行调解等。

把它们放在一起

现在您对 MVC 模式中涉及的层有了更好的理解,是时候通过本教程的示例应用程序来看看它的实际效果了。

打开教程的示例项目并运行应用程序。这个应用程序是一个类似于 WWDC 的应用程序,用于显示活动的会话和参与者。该应用程序在启动时从应用程序包中的 JSON 文件加载数据。加载数据会触发控制器更新视图并开始等待用户交互。

03.png

首先,展开模型文件夹。三个“纯”模型类EventAttendeeSession。请注意这些是如何符合Decodable从 JSON 响应中解析的很简单的。

从那里,您有其他模型文件,它们是您的网络调用的一部分。本教程的项目从应用程序包中加载文件,但您可以添加远程 URL 来加载文件或调用 Web 服务。主要课程有:

  • 网络:一个包装器URLSession
  • NetworkError:在请求/响应过程中处理不同的错误
  • WebService:添加用于加载与会者和会话的辅助方法

值得注意的是Network.swift,它有一个使用泛型从 URL 请求 JSON 并将其解析为您在方法调用中指定的对象类型的方法。它为您处理 HTTP 响应和代码、错误、解析、并发和URLSession调用。

这些文件很小,很简单,包含它们自己的逻辑,您最多可以使用一些模拟方法来测试它们。

继续查看视图,它主要包含在您的故事板文件中。这是一件好事,因为它不仅更直观,而且减少您编写的代码行数意味着减少您可能引入的错误数量。

您可能还会在文件中包含视图逻辑,例如SessionCell.swift,尤其是在您执行自定义动画或 UI 元素时。您的视图不应该了解业务逻辑、与模型对象交互或了解模型对象或做任何与视图本身无关的事情。

从纯单元测试的角度来看,在视图层测试组件并不是那么简单或有用,尤其是在处理 XIB 或故事板时。这是因为以编程方式创建视图或控制器不会触发视图生命周期中的所有方法或设置非编程约束。您可以进行 UI 测试并确保您的测试也覆盖视图层。

最后,你到了控制器层,这就是有趣的地方,因为它们充当了视图和模型之间的粘合剂。AttendeeViewController并且SessionViewController具有相同的概念。这些显示您可以选择查看详细信息的项目列表。当您呈现所选项目的详细信息时,您使用DetailViewController

需要注意的一些事项是:

  • AppDelegate负责加载事件数据,然后将其传递给每个控制器。
  • 当控制器接收到数据时,它会触发数据表的重新加载。
  • 控制器充当表视图的数据源和委托。
  • 当用户选择会话或事件时,控制器会处理传递详细信息。

这一切都非常精简、干净且易于理解。对于更健壮的项目,您甚至可以创建单独的类或对象来充当数据源或委托,从而进一步简化您的控制器。

没有严重依赖完成块、委托(在表视图需要之外)或在视图或控制器中混合业务逻辑。你也没有可怕的“MVC:海量视图控制器”。当您的太多业务逻辑位于控制器内部时,就会发生这种情况。

一些提示可以帮助您注意图层中的项目何时可能超出其边界:

  • 控制器正在执行网络请求、解析或持久化。
  • 控制器(或任何文件)有成百上千行代码。
  • 视图接受模型对象来“配置”自己或设置 UI 元素以进行显示。
  • 控制器正在执行大量业务逻辑、计算、数据处理或操作。

结论

您可以使用教程中的所有代码部分下载最终项目。

这里也推荐一些面试相关的内容!