iOS App 的最佳架构,存在么?

6,828 阅读7分钟

本文翻译自 The best architecture for the iOS app, does it even exist?,建议参考原文阅读,也可查看这里

前一段时间,我偶然发现了有关 iOS 体系结构模式的文章,标题颇具挑衅性:“唯一可行的 iOS 架构”。标题中问题的答案实际上是 MVC。简而言之,MVC 是 iOS 应用程序唯一可行的也是最好的架构。

该文章的主要思想是人们只是以错误的方式去理解 MVC。该 ViewController 实际上是表示层的一部分,而 Model 部分则代表整个 Domain Model,而不仅仅是某些数据实体。总的来说,我同意那个帖子的想法,但是如果我同意那个帖子的每一个陈述,我就不会写这篇文章了,不是吗?

我注意到作者基本上没有涉及格式良好的应用程序体系结构的一个非常重要的方面:使用单元测试(UT)覆盖了应用程序业务逻辑(BL)。对我而言,这是明智的应用程序体系结构的最重要因素之一。如果无法提取应用程序 BL 并以足够的覆盖范围实现 UT,那么这种架构简直糟透了。

此外,如果一个应用没有 UT,那么证明上述观点是不可行的,因此其架构最有可能出现问题。您可以向自己保证,在你从紧张工作中的片刻休息时间可以轻松实现UT,或者仅仅是因为您在 XCode 项目中拥有专用的“Tests”目标以及一些模板 UT。相信我,但这不过是一种幻想。我坚信,如果单元测试未随功能一起实施或在交付后不久就将无法实施。

以该声明为公理,应用程序体系结构必须提供将 BL 与 UI 表示分离并使其可测试的功能。很明显,由于 UIViewController 子类对生命周期的依赖性,因此它不是该角色的最佳候选人。这意味着负责 BL 的类必须位于 UIViewController 和 Services 之间,或者换句话说,位于 View 和 Domain Model 之间。

值得注意的是,这里的 Services 是指负责联网,与数据库、传感器、蓝牙、钥匙串、第三方服务等进行通信的逻辑。换句话说,是应用中多个位置、页面的共享部分。而图上的业务逻辑部分仅对应于一个页面或一个由视图控制器表示的页面组件。在开头提到的有关 MVC 的文章中,作者将 BL 和 Domain Model 部分结合在一起,同时接受 UIViewController 是表示逻辑(即视图)的一部分。

现在,当确定了将表示和业务逻辑分离的需要时,让我们考虑一下这两个部分如何相互通信。 这就是那些著名的架构模式出现的地方。

MVP

在 MVP 模式中,Presenter 和 View 通过协议相互链接。Presenter 被注入了 View 协议的实例,反之亦然,View 的协议必须具有足够的接口才能在 UI 中呈现原始数据,而Presenter 的协议必须具有传输从用户或系统接收到的事件(如触摸,手势,摇动等)的接口。UIViewController 子类在此处表示 View 部分,而 Presenter 类不能依赖UIKit(例如,有时需要导入 UIKit 才能对 UIImage 等数据类进行操作)。

在 iOS 上,由于 UIViewController 生命周期的工作方式,它必须具有对 Presenter 实例的强引用,而最后一个必须是弱引用,以避免循环引用。此配置使人联想到委托模式。 在大多数情况下,UIViewController 可能具有对 Presenter 的直接类型化引用,但在某些情况下,最后一个角色也可以通过协议注入到第一个中。如果 presentation 用于不同的业务逻辑,这可能会很有用。Presenter 到 UIViewController 的链接必须通过协议才能进行模拟,并用 UT 覆盖。在 Service 部分,我不会做太多具体说明,但是为了测试 Presenter,还必须将其与协议一起注入。

有关 MVP 的更多详细信息以及基本示例,请参见此处1

MVVM

在 MVVM 模式中,表示和业务部分使用响应性绑定相互通信,它们分别称为 View 和 ViewModel。在 iOS 中,通常会使用 ReactiveCocoa,RxSwift 或现代的 Combine 框架进行响应性绑定,它们通常位于 ViewModel 类中,并且也由 ViewController 通过协议使用。在与 Services 或 Domain Model 进行通信的一部分中,MVP 并没有太大的区别,但人们可能更喜欢在这里使用绑定或响应性事件。与前面的模式一样,必须在协议中注入依赖项,以便在 UT 中模拟它们。

可以在此处2找到有关 MVVM 的更多详细信息以及基本示例。

MVVM+Router

这里独立的主题是路由。在 iOS 中,以模态方式显示新屏幕或推送到导航堆栈是通过 UIViewController 子类来实现的。但是,这些操作可能是 BL 的一部分,并且可能会被 UT 覆盖,例如如果发生特定事件,则必须关闭屏幕。在这种情况下,将应用程序逻辑的这一部分分为一个称为 Router 的类是有意义的。因此,模式变为 MVP+R 或 MVVM+R。在某些来源中,您可能会发现此部分分别命名为 Coordinator 和 MVVP+C 或 MVVM+C。尽管协调器可能具有除路由之外的其他一些逻辑,但我更喜欢在概念上将它们等同。ViewModel 和 Router 之间的链接必须通过协议,并且最后一个必须仅负责屏幕操作,所有 BL 必须仍然集中在第一个中。因此,Router 不是 UT 的主题。

具有 MVVM+R 架构模式实现的示例项目可以在我的 GitHub3 上找到。

其它

VIPER iOS 体系结构模式是 MVVM+R 的扩展,其中 ViewModel 分为两部分:Interactor 和 Presenter。第一个负责与实体(即域模型)的通信。第二部分准备要在视图中呈现的模型类。老实说,我从未使用过这种模式,因为对我而言,它似乎过于分散和复杂。 MVVM+R 关注点分离对我而言总是足够的。

在 MVVM+R 中,每个模块(屏幕)必须至少显示 3 个类:ViewController,ViewModel 和 Router。并且必须有一个实例化所有这些部分并将它们彼此链接的位置,即模块构建的关键。最合适的位置是 Router,因为它没有与 iOS UIViewController 生命周期耦合,并且必须知道如何显示页面才能正确关闭它。但是,在某些情况下,将这一部分移到名为 Builder 的单独的类中会更方便,这就是 RIB(Uber的架构模式)中发生的情况。ViewModel 重命名为 Interactor,其余部分保持不变。这种模式具有 Uber 引入的一些更有趣的想法和技术,您可以在 RIB Wiki 上阅读。但是,我在 RIBs 代码库中发现的最实用的东西是 XCode 模板,当在项目中引入新的 RIBlet 时,它可以帮助避免样板编码。这些模板也可以很容易地用于 MVVM+R 类。为 Uber 的 iOS 工程师👏。

最后简单聊聊关于 iOS 上的单向数据流架构模式。如果看一下以上模式的方案,它们在组件之间都具有双向连接。在 Redux 中不是这种情况。这种架构模式最初是从 Web 迁发到移动应用的,尤其是 React 框架。在 ReSwift 框架中,此概念是 iOS 开发中最受欢迎的实现。我不会详细介绍,因为尚未在生产应用中使用此架构。但是,很明显,从 Web 开发进入 iOS 的人们发现这种架构模式最为直观和熟悉。

结论

什么才是最好的应用程序架构始终是一个热门的主题,所以现在我更倾向于约翰·桑德尔在他的最近一次演讲中提出的想法:

最好的架构是您和您的团队共同创建的架构,通过将标准模式和技术与系统设计相结合来适合您的项目。

参考

[1]https://medium.com/@saad.eloulladi/ios-swift-mvp-architecture-pattern-a2b0c2d310a3
[2]https://medium.com/flawless-app-stories/practical-mvvm-rxswift-a330db6aa693
[3]https://github.com/OlexandrStepanov/MVVM-RouterDemo