如何不急于MVVM实现

假设您有一个小项目,过去仅在两天内就交付了新功能。 然后,您的项目将变得更大。 交货日期变得不受控制,从2天到1周,然后是2周。 它使您发疯! 您一直在抱怨:一个好的产品应该没有那么复杂! 那正是我所面对的,对我而言确实是一个糟糕的时刻。 现在,在该领域工作了几年之后,与许多优秀的工程师合作,我意识到产品设计并没有真正使代码变得如此复杂。 是我让事情变得如此复杂。

我们可能有编写意粉代码的经验,这严重损害了我们项目的性能,问题是如何解决它? 好的架构模式可能会有所帮助。 在本文中,我们将讨论一种好的架构:Model-View-ViewModel(MVVM)。 MVVM是一种流行的iOS体系结构,其重点是将用户界面的开发与业务逻辑的开发分开。

术语“好的架构”听起来可能太抽象了。 也不知道从哪里开始。 这里有个提示:我们可以将重点放在如何提高代码的可测试性上,而不必关注体系结构的定义。 有太多的软件架构,例如MVC,MVP,MVVM,VIPER。很明显,我们可能无法掌握所有这些架构。 但是,我们仍然可以牢记一条简单的规则:无论我们决定使用哪种架构,最终目标都是使测试变得更简单。 使用这种方法,我们在编写代码之前就开始思考。 我们强调如何直观地区分责任。 而且,按照这种思维方式,体系结构的设计似乎清晰合理,我们不再局限于琐碎的细节

TL; DR

在本文中,您将学习:

  • 我们选择MVVM而非Apple MVC的原因
  • 如何调整MVVM以设计更清晰的架构
  • 如何基于MVVM编写一个简单的实际应用程序

您不会看到:

  • MVVM,VIPER,Clean等之间的比较
  • 解决所有问题的灵丹妙药

所有这些体系结构都有优点和缺点,但是它们都旨在使代码更简单明了。 因此,我们决定专注于为什么我们选择MVVM而不是MVC, 以及我们如何从MVC转向MVVM。 如果您对MVVM的缺点感兴趣,请参考本文末尾的讨论。

因此,让我们开始吧!

苹果MVC

MVC(模型-视图-控制器)是Apple推荐的体系结构模式。 定义可以在这里找到。 MVC中对象之间的交互如下图所示:

ViewController包含视图并拥有模型。 问题是我们曾经在ViewController中编写控制器代码以及视图代码。 它使ViewController过于复杂。 这就是为什么我们称其为Massive View Controller。 在为ViewController编写测试时,您需要模拟视图及其生命周期。 但是很难嘲笑观点。 而且,实际上,我们只想测试控制器逻辑就不想模拟视图。 所有这些使编写测试变得如此复杂。

因此,MVVM随时可以挽救。

MVVM —模型—视图— ViewModel

MVVM由John Gossman在2005年提出。MVVM的主要目的是将数据状态从View移到ViewModel。 MVVM中的数据流可以如下图所示:

每个UI组件在ViewModel中都有一个对应的属性。 我们可以说,在View中看到的应该与在ViewModel中看到的相同。

但是,我们该如何进行绑定呢?

封闭实现绑定

在Swift中,有多种方法可以实现“绑定”:

  1. 使用KVO(键值观察)模式。
  2. 使用第三方库进行FRP(功能性反应式编程),例如RxSwift和ReactiveCocoa。
  3. 自己动手制作。

使用KVO模式不是一个坏主意,但是它可能会创建一个巨大的委托方法,我们必须小心addObserver / removeObserver,这可能会对View造成负担。 绑定的理想方法是在FRP中使用绑定解决方案。 如果您熟悉函数式反应式编程,那就去吧! 如果不是这样,我不建议仅使用FRP进行装订,因为使用大锤将螺母弄碎会造成混淆。 这是一篇精彩的文章,谈论使用装饰器模式自己制作绑定。 在本文中,我们将把事情简化。 我们使用闭包绑定事物。 实际上,在ViewModel中,用于绑定的接口/属性如下所示:

处理用户交互

让我们继续进行用户交互。 在PhotoListViewModel中 ,我们创建一个函数:

当用户单击单个单元格时, PhotoListViewController使用此功能通知PhotoListViewModel 。 因此,我们可以在PhotoListViewController中重构委托方法:

这意味着一旦func tableView(_ tableView:UITableView,willSelectRowAt indexPath:IndexPath)-> IndexPath? 由于用户互动而被调用,该操作将传递给PhotoListViewModel 。 委托函数根据PhotoListViewModel提供的isAllowSegue属性决定是否进行筛选 。 我们已成功从视图中删除状态。 🍻

PhotoListViewModel的实现

这是一段漫长的旅程,对吧? 忍受我,我们正在触及MVVM的核心! 在PhotoListViewModel中 ,我们有一个名为cellViewModels的数组,该数组表示View中的表格视图。

我们如何获取数据并准备好阵列? 实际上,我们在初始化ViewModel时做了两件事:

1.注入依赖项: APIService 2.使用
API服务

在上面的代码片段中,我们在开始从APIService提取数据之前将属性isLoading设置为true。 由于我们之前所做的绑定,将isLoading设置为true意味着View将打开活动指示器。 在APIService的回调关闭中,我们处理获取的照片模型并将isLoading设置为false。 我们不需要直接触摸UI组件,但是很明显,当我们更改ViewModel的那些属性时,UI组件可以按预期工作。

然后是processFetchedPhoto(photos:[Photo])的实现

它完成了一个简单的工作,将照片模型包装到一个PhotoListCellViewModel数组中。 更新属性cellViewModels时 ,View中的表视图将相应地重新加载。

是的,我们制作了MVVM🎉

可以在我的GitHub上找到示例应用程序:

koromiko /教程

教程– https://koromiko1104.wordpress.com的代码

github.com

您可能想尝试MVC版本(标签:MVC),然后尝试MVVM版本(最新提交)。

概括

在本文中,我们成功地将一个简单的应用程序从MVC模式转换为MVVM模式。 和我们:

  • 使用闭包制作了具有约束力的主题。
  • 从视图中删除了所有控制器逻辑。
  • 创建了一个可测试的ViewModel。

讨论区

正如我上面提到的,架构都具有优点和缺点。 阅读我的文章后,您必须对MVVM的缺点有一些想法。 有很多好的文章讨论了MVVM的坏处,例如:

MVVM不是很好-Soroush Khanlou
iOS上的MVVM问题— Daniel Hall

我对MVVM的最大担心是ViewModel做太多事情。 正如我在本文中提到的,ViewModel中包含控制器和演示者。 另外,MVVM模式中不包括两个角色,即构建器和路由器。 我们曾经将构建器和路由器放在ViewController中。 如果您对更清晰的解决方案感兴趣,则可能需要检查MVVM + FlowController(使用FlowControllers改进iOS架构)和两个著名的架构,VIPER和Bob叔叔的Clean。

从小开始

总会有更好的解决方案。 作为专业工程师,我们一直在学习如何提高代码质量。 像我这样的开发人员过去常常被如此多的体系所淹没,并且不知道如何开始编写单元测试。 因此,MVVM是开始您的旅程的好地方。 它很简单,并且可测试性仍然很好。 在Soroush Khanlou的另一篇文章“帮助您销毁Massive View Controller的8种模式”中,有很多好的模式,而MVVM也采用了其中的一些。 而不是被庞大的架构所束缚,我们如何开始使用小型但功能强大的MVVM模式编写测试?

“成功的秘诀就是开始。” —马克·吐温

在下一篇文章中,我将继续谈论为我们的简单Gallery应用程序编写单元测试。 敬请关注!

如有任何疑问,请随时发表评论。 也欢迎任何讨论! 感谢您的关注。

参考文献

用于构建WPF应用程序的Model / View / ViewModel模式简介— John Gossman
MVVM简介— objc
iOS体系结构模式— Bohdan Orlov
快速的Model-View-ViewModel — SwiftyJimmy
Swift教程:MVVM设计模式简介— DINOBARTOŠAK
MVVM —用MVVM编写可测试的表示层— Brent Edwards
绑定,泛型,Swift和MVVM — Srdan Rasic