如何选择合适的iOS体系结构(第2部分)

MVC,MVP,MVVM,VIPER或VIP

您可以在这里查阅第一部分。

主要的iOS架构

简要概述。

MVC

MVC层如下:

M:业务逻辑,网络层和数据访问层

V:UI层(UIKit内容,情节提要,Xibs)

C:在模型和视图之间协调中介。

要了解MVC,我们必须了解它的发明背景。 MVC是在旧的Web开发时代发明的,当时Views没有状态。 在过去,每次需要在网站上进行视觉更改时,浏览器都会重新加载整个HTML。 当时还没有维护和保存视图状态的概念。

例如,有些开发人员将同一个HTML文件,PHP和数据库访问混合在一起。 因此,MVC的主要动机是将View层与Model层分开。 这增加了模型层的可测试性。 在MVC中,视图和模型层应该彼此一无所知。 为了使之成为可能,发明了一个名为Controller的中间层。 这就是所应用的SRP。

MVC周期的一个示例:

  1. 触发了View层中的用户操作/事件(例如:Refresh Action),并将该操作传达给Controller
  2. 向数据层询问数据的控制器
  3. 建模返回到控制器的数据
  4. 管制员说,使用新数据为View更新他的状态
  5. 查看更新他的状态

苹果MVC

在iOS中,视图控制器耦合到UIKit和生命周期视图,因此它不是纯MVC。 但是,在MVC定义中,没有什么可以说Controller不知道特定于View或Model的实现。 他的主要目的是将Model层与View层的职责分开,以便我们可以重用它并单独测试Model层。

ViewController包含视图并拥有模型。 问题是我们曾经在ViewController中编写控制器代码以及视图代码。

MVC通常会产生一个称为Massive View Controller的问题,但这只会发生,并且在具有足够复杂性的应用程序中会成为一件严重的事情。

开发人员可以使用一些方法来使View Controller更易于管理。 一些例子:

  • 使用委托设计模式为其他类(如表视图方法数据源)提取VC逻辑,为其他文件提取VC逻辑。
  • 通过组合创建更加明确的职责分离(例如,将VC拆分为子视图控制器)。
  • 使用协调器设计模式来消除在VC中实现导航逻辑的责任
  • 使用DataPresenter包装器类,该包装器类封装逻辑并将数据模型转换为表示呈现给最终用户的数据的数据输出。

MVC与MVP

MVC是向前迈出的一步,但它仍然某些事情的缺席或沉默标志

同时,万维网发展壮大,开发人员社区中的许多事物也在发展。 举例来说,程序员开始使用Ajax,只加载部分页面,而不是一次加载整个HTML页面。

在MVC中,我认为没有任何迹象表明Controller不应该了解View(不存在)的具体实现。

HTML是View层的一部分,很多情况下都是愚蠢的。 在某些情况下,它仅接收来自用户的事件并显示GUI的可视内容。

随着网页的各个部分开始被装入各个部分,这种细分导致保持View状态的方向以及对表示逻辑职责分离的更大需求。

表示逻辑是控制应如何显示UI以及UI元素如何交互的逻辑。 一个示例是控制逻辑,该逻辑在什么时候加载指示器应开始显示/动画以及何时应停止显示/动画。

在MVP和MVVM中,视图层应该是愚蠢的,没有任何逻辑或智能,在iOS中,视图控制器应该是视图层的一部分。 View愚蠢的事实意味着即使表示逻辑也不在View层之内。

MVC的问题之一是不清楚表示逻辑应保留在哪里。 他只是对此保持沉默。 表示逻辑应该在视图层还是模型层中?

如果模型的作用是仅提供“原始”数据,则意味着视图中的代码为:

考虑以下示例:我们有一个用户,名字和姓氏。 在视图中,我们需要将用户名显示为“ Lastname,Firstname”(例如“ Flores,Tiago”)。

如果模型的作用是提供“原始”数据,则意味着视图中的代码将是:

 让firstName = userModel.getFirstName() 
让lastName = userModel.getLastName()
nameLabel.text = lastName +“,” + firstName

因此,这意味着View将负责处理UI逻辑。 但这使得UI逻辑无法进行单元测试。

另一种方法是让模型仅公开需要显示的数据,从而从视图中隐藏任何业务逻辑。 但是,然后,我们得到处理业务和UI逻辑的模型。 它可以进行单元测试,但是模型最终隐含地依赖于View。

 让名称= userModel   getDisplayName () 
nameLabel.text =名称

MVP对此很清楚,并且呈现逻辑保留在Presenter层中。 这增加了Presenter层的可测试性。 现在,模型和演示者层可以轻松测试。

通常,在MVP实现中,视图隐藏在接口/协议的后面,并且在Presenter中不应引用UIKit。

要记住的另一件事是传递依赖。

如果控制器将业务层作为依赖关系,而业务层将数据访问层作为依赖关系,则控制器对数据访问层具有可传递的依赖关系。 由于MVP实现通常在所有层之间使用协定(协议),因此它没有传递依赖项。

不同的层也由于不同的原因和速率而改变。 因此,当您更改图层时,您不希望它在其他图层中引起次要效果/问题。

协议比类更稳定。 协议没有实现细节和合同,因此可以更改一个层的实现细节而不影响其他层。

因此,合同(协议)在层之间创建了去耦。

MVP与MVVM

MVP和MVVM之间的主要区别之一是,在MVP中,Presenter通过接口与View通信,而在MVVM中,View面向数据和事件更改。

在MVP中,我们使用Interfaces / Protocols在Presenter和View之间进行手动绑定。
在MVVM中,我们使用RxSwift,KVO之类的东西或具有泛型和闭包的机制进行自动数据绑定。

在MVVM中,我们甚至不需要ViewModel和View之间的合同(例如:java接口/ iOS协议),因为我们通常通过观察者设计模式进行通信。

MVP使用委托模式,因为Presenter层将订单委托给View层,因此即使只是接口/协议签名,它也需要了解有关View的一些知识。 考虑一下通知中心和TableView委托之间的区别。 通知中心不需要接口来创建通信通道,但是TableView Delegates使用了类应实现的协议。

考虑一下加载指示器的表示逻辑。 在MVP中,演示者执行ViewProtocol.showLoadingIndicator。 在MVVM中,ViewModel中可能存在isLoading属性。 View层通过自动数据绑定来检测此属性何时更改并刷新。 MVP比MVVM更重要,因为Presenter发出命令。

MVVM与数据更改有关的不仅仅是直接订单,我们在数据更改和视图更新之间建立关联。 如果将RxSwift和功能性反应式编程范例与MVVM一起使用,我们将使代码的命令性和声明性降低。

MVVM比MVP更易于测试,因为MVVM使用观察者设计模式,该模式以分离的方式在组件之间传输数据。
因此,我们仅通过比较两个对象即可查看数据变化,而无需创建模拟方法来测试View和Presenter之间的通信,从而进行测试。

PS:我对文章进行了一些更新,使之增长很多,因此有必要将其分为三个部分。 您可以在此处阅读第三部分。

第二部分到此结束。 欢迎所有反馈。 第三部分将讨论VIPER,VIP,反应式编程,权衡,约束和上下文感。

感谢您的阅读! 如果您喜欢这篇文章,请鼓掌
👏🏻这样其他人也可以阅读🙂