为什么在 iOS 中使用视图模型 (MVVM)?

Why use a View Model (MVVM) in iOS?

因为 Swift/Objective-C 支持编写扩展,我将我的 "ViewModel" 属性写在模型的扩展 class 中,并在通常使用 ViewModel 的地方使用此扩展。

我知道扩展不能有存储属性。但是无论如何,大多数 MVVM 架构都不推荐在 ViewModel 中存储属性(用于缓存的属性除外)。

我维护单独的 ViewModel 对象的主要问题是让它与模型同步。是的,有很多第三方框架可以帮助使用反应式编程技术进行同步。但是能解决的问题,简单的用一个扩展,为什么要用一个笨重的框架来实现呢?

我的基于扩展的 MVVM 架构没有遇到障碍。你们中有人尝试过这个并转向响应式编程架构吗?

ViewModel 应该是架构 POV 的不同实体,而不是代码放置 POV 的实体。您对模型进行扩展的 MVVM 方法使模型了解太多并违反了 SRP,例如如果模型有一些日期属性,视图模型通常会使用当前语言环境来格式化它。如果模型是数据存储,它应该只包含相关的数据结构,而没有方法。如果模型是一项服务(例如网络客户端),它应该只包含具有最少数据转换的相关方法(例如 json 到结构)。

另外,在我的实践中,我有时会在一个模型上使用多个 VM。比方说,如果我有一个具有 firstNamelastNameemail 属性的 User 模型,我可能需要 fullName 属性 来显示它table 行(在 UserCellViewModel 中实现),但在详细视图中显示用户信息时的所有三个属性(在 UserDetailViewModel 中实现)。通过对模型的扩展,所有四个属性(fullName 在扩展中实现)都可以在所有上下文中访问。让使用 ViewModel 的控制器知道的越少越好(需要引用)。这可以在 Objective-C 中实现,其中您的扩展可以拥有自己的 header/interface 文件,但您在 Swift 中无法实现。

MVVM 的好处 ViewModel 是放置用户输入验证逻辑、视图呈现逻辑、网络请求的绝佳位置。结果,ViewController 文件变得不那么臃肿了。 此外,View-ViewController 组件与模型完全隔离。因此在开发过程中,一名开发人员可以处理屏幕的 UI 部分,而另一名开发人员可以独立并同时处理屏幕的逻辑。 很容易重新设计 UI 而不会弄乱模型逻辑,因为它们都是完全隔离的。只要您 link 为 ViewModel 提供适当的属性,就可以将视图组件换入和换出。这为使用 UI 进行试验提供了更多自由。 对于通用应用程序,iPad 和 iPhone ViewController 都可以与同一个 ViewModel 交互。 它更容易测试。开发人员可以在没有 View 的情况下为 ViewModel 和模型创建单元测试。

我对扩展方法的糟糕体验是 my March 2016 talk at iOSoHo in New York 第一部分也是最重要部分的基础。我所说的关于扩展的大部分内容都不在幻灯片中。扩展方法存在几个问题:

  1. 你永远只能有一个模型到视图的映射(没有另一个中间层来提供逻辑协调扩展属性)。这极大地损害了视图的可重用性。
  2. 关于第 1 点,即使您围绕这一点进行协调,如果您在很多地方使用该模型,例如 name 然后稍后添加 firstNameOnly,等等。如果您很想以这种方式行事,那么模型绝对只能以一种方式符合协议。
  3. 提供虚拟值代替模型来进行自动布局、调试、错误情况等几乎是不可能的。您可能会发现自己为此编写了一个结构——即 ViewModel。
  4. 如果您的视图需要多个模型来填充它,或者需要模型外部的信息,视图模型可以简化它们的协调。

有了 ViewModel,这些重要的案例就变得微不足道了。 (在演讲中我称"commands"是因为它们完全符合四人组设计模式命令的定义;设计模式注释也被称为作为动作,即 UIAlertAction。)

我认为 "ViewModel" 应该是轻量级的、不可变的,并且通过单向数据流分布。它们存储了属性,但这只是为了移动数据,虚拟机本身是不可变的和一次性的。这类似于 Facebook 在 iOSoHo 2017 年 3 月提到的方法,尽管我认为他们不称它们为视图模型,只是轻量级对象。您可以在其中放置更复杂的逻辑,但实际上最好最多调出其他地方处理的更复杂的逻辑。

我认为有一个补充是关键,我的规则是 "ViewModel",无论您怎么称呼它,都不应具有程序员查看线框无法编写的属性。所以 var dateString : String 而不是 var date : NSDate。这是我在幻灯片中的厚颜无耻的术语,"Modal Object Attribute Transformer"。但它是一个重要的要点:一座城堡需要能够在被围困时升起吊桥并继续前进;当网络被切断(或尚不存在)时,视图需要完全发挥作用。在如此多的场景中,与您的视图可以做到这一点相比,您会轻松得多。

就在几周前,我采用了这种方法,能够在大约 4 天内创建多个复杂的屏幕,仅从设计开始,最终将由网络数据填充,但目前没有任何模型或网络层任何。当数据层准备好后,我可以简单地写一些创建方法 init(with: Model) 或更好 static func withModelforCase1(_ model: Model) -> ViewModel 并在创建方法中写入映射,然后在连接模型时,将 static func debugCase1() -> ViewModel 替换为这些创作方法。随着模型的发展,编译器将在此处且仅在此处抛出错误;随着视图的发展,调整 ViewModel,编译器将再次在此处且仅在此处抛出错误。无需修改其他代码。

扩展一开始看起来很优雅,但实际上在模型和视图之间建立了紧密的耦合。某种 ViewModel 方法是一个强大的系统,可以满足现代 iOS 应用程序和现代 iOS 应用程序开发的需求。