Swift中MVVM的面向协议的技巧

嗨伙计。 最近,MVVM已成为iOS应用程序的某种标准架构。 它提供了很好的关注点分离,格式化数据的好方法以及使用诸如RxSwift之类的框架的出色视图绑定机制。 在这篇文章中,我将提供一些技巧来简化该模式的实现。

可重用使视图变得简单

使用MVVM,视图与体系结构其余部分之间的分离非常清晰。 视图包括UIViewControllers及其出口。 实际上,实例化View变得越来越重要,特别是因为诸如Coordinator之类的模式越来越受欢迎。 在本文的其余部分中,我们将假定您正在实现这种体系结构。

Reusable是一种API,它带有对UIViews和UIViewControllers的便捷扩展,可以方便地以类型安全的方式实例化它们。

这是GitHub存储库:可重用。 它是与Carthage,CocoaPods和SPM兼容的轻量级API。 不使用它带来的幸福🖖真可惜。

基本上,Reusable提供了mixins(具有默认实现的协议),一旦您使它们符合适当的协议,它们就会向UIViews和UIViewControllers添加实例化函数。

在使用Coordinator模式时,有时需要实例化UIViewControllers并将其传递给ViewModels。 幸运的是,因为可重用在此方面有很大帮助。

这是将Reusable用于UIViewControllers实例化所需要做的事情:

  • 为每个UIViewController创建一个Storyboard文件(当然,如果在同一个Storyboard中可能有多个UIViewController,但是为了简单起见,我们将仅考虑一个UIViewController)
  • 将UIViewController设置为场景中的初始ViewController
  • 创建一个UIViewController文件,其中ViewController类名与Storyboard文件名相同。 例如,如果故事板文件名为“ SettingsViewController.storyboard”,则UIViewController类将命名为“ SettingsViewController”
  • 使UIViewController实现“ StoryboardBased”协议

就是这样。 现在,您可以使用单行代码实例化ViewController:

最酷的是, settingsViewController的类型是SettingsViewController ,不需要强制转换语句。

实际上,基于Storyboard的协议非常简单。 让我们深入了解一下:

基本上,它的作用是为实现该协议的每个UIViewController提供静态的“实例化”功能。 此函数返回UIViewController的实例。 由于“ Self”是函数的返回类型,因此类型推断可确保我们不必强制转换结果。

我强烈建议您深入研究可重用。 从类型安全的方式从Xib实例化UIView或使UITableViewCells出队时,这也将很有帮助。

面向协议的ViewModel

在当今的应用程序中,类似协调器的体系结构很常见,尤其是当与MVVM模式组合时。 这就是为什么我希望首先谈论Reusable的原因。

但是我发现有一个技巧非常有用,并且是可重用的补充。 它非常适合面向协议的方法中的MVVM模式。

这个想法不仅是为了简化UIViewControllers的实例化,而且是提供一种很好的方式来传递它们相关的ViewModels。 让我们写一个协议来定义拥有ViewModel的含义。

现在,我们可以将其与StoryboardBased混合使用,并提供一个静态函数,该函数以ViewModel作为参数实例化UIViewController。

条件扩展是一个非常强大的工具。 结合了“ StoryboardBased”和“ UIViewController”的“ where”语句使Self.instantiate函数可用,因此我们只需要将此调用包装在另一个设置UIViewController.viewModel属性的静态函数中即可。

假设我们有一个符合ViewModelBased协议的MyViewController:

使用ViewModel进行实例化将非常容易:

让我们进一步了解ViewModel抽象

到目前为止,我们仍然必须实例化ViewModel并将其提供给View。 仅实例化View并以通用方式处理ViewModel实例,不是很好吗? Swift类型推断可以在此方面大有帮助。

在深入研究代码之前,我想提醒您一些人可能会说这种技术在View和ViewModel之间引入了强大的耦合。 从某种意义上说,这是正确的,但是取决于分配给应用程序的时间,精力和复杂性,无论如何它都是一种有效的策略。

首先,我们将定义什么是ViewModel。 当然,我们将为此使用协议。 通过这样做,我们将介绍Services的概念。 服务是ViewModel检索数据或执行操作所需的低层。

我们必须修改ViewModelBased定义,以在关联类型中引入ViewModel协议。

最后,我们可以像这样修改ViewModelBased扩展:

此版本与上一个版本有2个主要区别:

  • 第一个区别是显而易见的:此静态函数不仅实例化UIViewController,而且实例化ViewModel。 这是开发人员不再需要做的一件事👍
  • 第二个区别是功能签名。 现在,它将某种服务作为参数。 如您所见,这是一个泛型函数。 “ where”语句强制开发人员传递与ViewModelType中要求的相同的ServicesT。 这带来了安全性和一致性consistency

很棒的是,Swift将根据ViewModelBased实现来推断ViewModelType。

让我们看看实际情况。

首先,为演示起见,我们必须定义一个哑服务:

现在,我们可以定义需要此服务的ViewModel:

带有ViewModel的MyViewController实例化变得如此简单(考虑到我们已经有一个MyService实例):

服务协议组成

尽管这似乎很方便,但是这种模式有一个缺点:如果ViewModel需要多个Services怎么办?

一种解决方案是传递某种提供您应用程序所有服务的容器。 这会起作用,但不是很安全,因为ViewModel可以不受限制地使用容器的每个服务。

我曾经读过Krzysztof Zablocki关于此问题的帖子(在此),尽管使用ViewModel方法可以非常轻松地使用它。

假设我们的应用程序需要3个服务:

这个想法是使用协议组成来表达我们在ViewModel中所需的服务。 我们将为每个服务定义一个协议,以授予对该协议的访问权限:

现在,在我们的ViewModels中,我们能够以精细的粒度清晰地定义我们的依赖关系:

最后一步是定义依赖容器:

而且我们很高兴,我们现在可以以不错的安全性但可伸缩性将容器传递给ViewModel。 如果我们需要访问ViewModel内部的另一个Service,我们只需要更新协议组成即可。

最后,UIViewController实例化是相同的(考虑到MyViewController2是基于ViewModelBase的VC):

等等。

我希望这有帮助。 我很高兴阅读您对这些技巧的反馈。

敬请关注。