【翻译】打造更好的 MVC (一)

原文:A Better MVC, Part 1: The Problems

我最近读了 @radiantav 的一篇 Much ado about iOS architecture 的精彩文章。它触及到了我脑海中经常出现的一个话题。我在最近的 Swift by Northwest 上发表了一篇名为「打造更好的 MVC 」的演讲。下面我会用一系列博客文章来阐述我演讲的要点。

我首先要道歉,文章中不会有任何照片。使用图片肯定会让主题更加清晰,但我不想花时间去制作它们。你只能多运用你的想象力了。

MVC 的问题

人们谴责 MVC 的主要原因是他们在使用它时往往会遇到两个主要问题:

  1. 如 Apple 示例代码所述,MVC 鼓励你违反封装原则,这最终会导致面条式代码。
  2. 没有适当的规则,你的视图控制器会变得庞大,导致 「MVC」会被大家调侃成「Massive View Controller」。

违反封装

遇到类似这样的 UITableViewDelegate 方法很常见:

1
2
3
4
5
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let item = maybeItem else { return }
let detail = MyCustomDetailViewController(item: item)
show(detail, sender: self)
}

这里有两个巨大的违反封装的问题。

第一个是这行代码:

1
let detail = MyCustomViewController(item: item)

封装原则表明,一个东西应该只是真正「知道」它所包含的对象。但是,这一行代码构建了后续的详细视图控制器信息,这就需要当前视图控制器知道整个数据流序列。

换句话说,这个列表不仅仅是一个列表,还必须知道它的具体使用方式。

第二个问题跟第一个相似:

1
show(detail, sender: self)

同样的,这违反了「不知道你的使用上下文」的原则。并且,你应该注意到,无论你是手动创建,还是调用 segue 并使用 prepareForSegue:sender: 来显示详细试图控制器,这些批评都适用。

Massive View Controller

很遗憾,我们得承认遇到数千行的视图控制器很常见。我们的试图控制器很快就会变得混乱,它会充斥着网络回调、代理方法(delegate methods)、数据源方法(data source methods)、IBActions、trait collection reactions、模型数据观察(model observation)和一些我们的业务逻辑。如果你正在进行任何类型的渐进式加载,并希望通过一个指示器来显示「正在加载…」,这时候你通常会需要添加几百行代码,管理另一个有加载状态的属性。

我们最终会遇到这种情况,因为我们对视图控制器没有足够的规则来约束。它们很快就会变得不可维护、脆弱,而且本质上是不可测试的。试问,谁想调试一个「5000+」行代码的文件?

解决 MVC 问题

根据我的经验,这些几乎是开发人员发现其他架构模式如此吸引人的两个基本原因。因此,我们转向其他架构模式来尝试和修复这些问题。我常见的其它架构模式有 MVVM、React、MVP、FRP、VIPER,或 insert-new-architecture-here

需要明确的是,这些模式都是没有问题。它们都使用了某种独特有趣的方式来解决问题。我们鼓励大家学习和了解它们。但是我发现基于这些模式构建应用程序往往也会带来一些负面的影响。

  • 所有这些其它的架构模式对于 UIKitAppKit 都不是很友好。因为这个问题,你在开发时往往会遇到许多额外的障碍。
  • 当你的团队成员变化时,你还需要额外的工作来向新的开发者讲述正在使用的构架模式,而不仅仅是业务逻辑。这需要更多的前期投入,并且在团队成员开始工作之前需要更长的准备时间。
  • 当操作系统发生变化时,你可能也需要一些额外工作来做一些适配。比如,必须检查大小类、安全布局边距、动态类型、本地化、区域设置更改、首选属性(状态栏样式等)、生命周期事件、状态保存和恢复等等。
  • 当你的需求发生变化时,你有时会遇到不得不 fork 其他人的库、等待他们更新,或者希望他们接受你的 PR 的情况。我不会在这里讨论使用第三方依赖库的优缺点,但是架构依赖比普通依赖会有更多「问题」要考虑,因为它是用来支撑整个应用程序。

使用其它的架构模式可能使你的代码干净简洁。但是,当你越来越远离系统框架提供的内容时,这意味着当新的系统功能出来时,你需要更多代码和更多时间来实现新的功能。

如果你可以只使用 UIKitAppKit 并且可以解决以上所讲的问题,那是不是很棒?如果我们可以生活在一个视图控制器中的代码不会超过300行的世界里,那是不是很好?

在下一篇文章中,我们将看看如何解决第一个问题。