如何将RxSwift与MVVM模式一起使用-第1部分

本文全部涉及如何将RxSwift与MVVM结合使用。 几年来,RxSwift一直是swift社区中的热门话题,但是我设法避免了它。 我花了一段时间将自己的大脑切换到一切都是可观察的状态。 一开始我也遇到了麻烦,要弄清楚何时使用VariableObservablePublishSubject以及如何将值绑定到UI组件。 我将在此博客中介绍所有这些主题。 我将展示如何在MVVM中使用RxSwift,在RxSwift中使用UITableView ,如何编写网络层以及如何测试RxSwift应用程序。 我不会从头开始讲解MVVM模式,但是在阅读该系列文章之后,您将可以在MVVM中使用RxSwift。 如果您想学习MVVM模式的基础知识,建议您阅读我先前发布的带有Swift应用程序的MVVM。

我将通过展示如何编写一个名为Friends的应用程序来介绍这些主题。 您可以从GitHub获取该应用程序的完整源代码,只需签出RxSwift分支即可。 18个月前,我使用不带RxSwift的MVVM编写了该应用程序。 现在,我认为将其重构并查看将RxSwift与MVVM一起使用时的外观会很好。 朋友是一个iPhone应用程序,它下载朋友列表并将其显示在该应用程序中。 您还可以添加,删除和更新朋友。 因此,这是一个简单的应用程序,具有足够的复杂性,可以满足iOS应用程序的许多基本需求。 这也是开始学习如何将RxSwift与MVVM结合使用的好地方! 顺便说一句,后端是使用Vapor快速编写的!

在应用程序的第一部分,我将展示将RxSwift与MVVM结合使用的基础知识。 设置正确的CocoaPods。 在ViewModel和视图之间绑定数据。 使用UITableView ,显示一个加载指示器以及如何向用户显示错误。 我们将首先浏览实现的ViewModel端,然后是视图。

首先,我们需要将RxSwift添加到项目中。 在此示例中,我们将使用CocoaPods,但您也可以使用Carthage和Swift Package Manager。 查看GitHub仓库以获取更多信息。

在pod文件中,您需要添加:

RxSwift添加了基本库,包括ObservableVariablePublishSubject等。RxDataSources包括与UITableView和UICollectionView相关的反应式库。 RxSwiftExt帮助将可观察对象直接绑定到UI组件。

我们还将为我们拥有的测试目标添加所有库。 在完成Podfile的编辑之后,我们需要在终端中运行pod install

在如何将RxSwift与MVVM结合使用的第一部分中,我们将专注于应用程序的第一个视图:

第一个视图具有一个表视图,该表视图显示了从后端加载的所有项目。 为了展示所有这些,我们将FriendTableViewViewModelFriendTableViewController 。 让我们从ViewModel开始。

ViewModel是使数据为视图准备就绪的模块(在本例中为FriendTableViewController)。 ViewModel还是我们放置大多数业务逻辑的地方。 我说“最”是因为我们应该避免在ViewModel变成另一个转储所有代码的地方的情况。 您可能听说过MassiveViewController问题,但我们也不想以MassiveViewModel结尾。 因此,如果可以将某些内容重构为其自己的模块,则应始终尝试这样做。 但是我敢肯定,您很想看看代码,所以让我们看看吧!

首先,我们将导入RxSwift,以便可以使用库提供的工具。 在import语句下,有一个枚举FriendTableViewCellType 。 该枚举包含表格视图可以显示的所有单元格类型。 类型为正常单元格,错误和空单元格。 普通单元格显示朋友的数据。 错误单元格向用户显示错误信息,并且服务器上没有数据时显示空单元格。 我们将检查如何在视图控制器代码中更具体地使用它们,但是现在,这是我们需要知道的。

变量与可观察值

然后我们可以从RxSwift的东西开始! 在代码块的底部,您可以看到两个定义为VariableVariable是RxSwift提供的一种类型。 它是最简单的类型,因此是开始观察RxSwift可观察对象的好地方。 RxSwift中的Observables对象通过发出onNextonErroronCompleted事件来更改其状态。 但是,通过Variable ,您可以使用value属性来设置新值。 当我们想要订阅一个Variable ,我们需要使用asObservable()函数。 完成所有设置后,只要更改值,便会通知观察者。 此外,可以确保Variable不会发出错误,因此它使得在视图控制器端的处理变得更简单。

在这里,我们已将loadInProgress定义为VariablesCells包含cellViewModels,这些单元在构造单元时使用。 每次从服务器收到包含好友数据的有效响应时,都会创建该数组的值,并且仅在启动对服务器的新请求时,该值才会更改。 cells是私有成员,因此该单元格值只能由ViewModel更改。 这样,就不会在视图控制器侧意外更改该值。 作为一对私有cells变量,我们有一个friendCells Observable 。 它是一个计算的属性,它返回一个可观察的cells变量。 这是我们稍后在视图控制器端将单元格值绑定到tableView的变量。

每当此类正在执行网络请求时,都将使用loadInProgress变量。 正如我们上面讨论的cells变量一样,它也被定义为私有。 loadInProgress还具有一个公共的计算属性onShowLoadingHud 。 它被定义为一个Observable ,并且将loadInProgress返回为observable。 这是我们将在视图控制器侧绑定到的变量,以显示加载状态。 请注意distinctUntilChanged ,这意味着该值仅在更改后才会发出。

发布主题

现在,让我们检查定义为PublishSubjectPublishSubject接收信息,然后将其发布给订阅服务器。 在这里,接收到的主题定义为SingleButtonAlert ,这也是它将发布给接收者的主题。 使用onNext()函数以与所有可观察对象相同的方式发出该值。 因此,使用PublishSubject与使用变量非常相似,但是代替设置value ,我们将调用onNext() 。 我认为我们也可以将简单的VariableonShowError一起使用,但是我想使用PublishSubject来涵盖PublishSubject更多类型。

SingleButtonAlert是一种类型,它定义标题,消息和按钮标题,并带有向用户显示警报类型的操作。 该代码很容易解释,您可以在此处检查该类。

这里的最后两个成员是appServerClientdisposeBagAppServerClient是对服务器执行所有请求的组件。 所有代码都可用,但是我将在另一篇文章中深入网络层。

那么什么是DisposeBag

最后但最重要的变量之一是DisposeBag 。 要销毁一个Observable ,我们应该始终对其调用dispose() 。 手动处理处置将非常艰巨,因此RxSwift为我们配备了DisposeBag 。 创建Observable ,应始终通过调用.disposed(by:)将其添加到disposeBag中。 这样,当disposeBag被释放时,它将对所有可观察对象调用dispose() ,这将照顾它们已使用的内存。 因此,在视图模型内部,我们定义了自己的disposeBag。 当视图模型被释放时,所有可观察对象也被释放。

使用这些简单的变量,我们已经可以看到ViewModel和View之间的数据绑定非常简单! 在视图控制器方面,我们只需要订阅这些变量,就可以完成数据绑定。 无需使用任何其他数据绑定技术(例如我们在“如何使用MVVM”教程中使用的Bindable )或委托,因为RxSwift可以为我们完成所有工作! 多么酷啊! 🙂

如前所述,我们将使用AppServerClient来处理服务器请求。 每次将请求发送到AppServerClient ,它都会返回一个Observable 。 让我们看看从AppServerClient获取朋友列表时的外观:

因此,我们定义了一个功能getFriends() 。 要做的第一件事是每当我们调用此函数时向用户显示加载指示符。 这是通过将loadInProgress变量的值设置为true来完成的。 之后,我们将从appServerClient调用getFriends()并订阅它返回的可观察对象。 现在,我们将开始监听它可以发出的不同值。

Observable接收到新值时,它将发送一个包含该值的事件。 我们可以订阅该事件,然后遍历该事件可能具有的所有状态,并取消包装该事件内部的值。 但是,有一种更简单的方法。 RxSwift还提供了可用于不同状态的订阅功能。 因此,我们不必像以前那样一直检查发出哪个事件,而是可以直接为不同状态定义块。 事件可以是onNextonErroronCompletedonDisposed

在这里,当调用onCompletedonDisposed时,我们不需要释放任何内存,因此我们仅处理onNextonError状态。 在onNext内部,我们首先将loadInProgress设置为false。 然后,我们将检查接收到的friends数组是否包含项目。 如果为空,我们将[.empty]单元格设置为friendCells的值。 如果有值,我们将使用compactMap将好友项转换为单元格视图模型,并为cell设置值。

onError内部,我们再次隐藏了loadingHud。 然后,我们将friendCells.value设置为[.error] ,对于消息,我们将使用扩展friendCells.value提供的错误值转换为正确的错误消息:

我们需要做的最后一件事是将这个可观察的对象添加到disposeBag中,以便在释放视图模型时将其丢弃。

现在,我们已经涵盖了视图模型。 让我们继续到视图控制器端。

在视图控制器中,我们将使用RxDataSources进行tableView处理,并使用RxSwiftExt将可观察对象直接绑定到UI组件。 在这一部分中,我们还将集中精力向用户展示loadHud和错误。 我们还将将friendCells值绑定到tableView,并了解如何删除朋友。

在课程开始时,我们将注意到视图模型定义。 这也是我们将在其中创建视图模型的地方,因为这是应用程序的第一个视图。

viewDidLoad ,我们将调用viewDidLoad函数:

首先,我们将通过绑定bindViewModel()中的所有值来准备视图模型。 然后,我们将设置单元格删除和点击。 调用完这些函数后,视图已完全建立,我们可以使用getFriends()函数开始下载数据。

使用RxSwift和MVVM绑定tableView数据源并处理委派

接下来,让我们检查bindViewModel()函数:

首先,我们将friendCells绑定到tableView。 您可能还记得, friendCellscells的计算属性,它从cells变量返回observable 。 之后,我们将调用bind(to:)并将tableView.rx.items作为参数。 tableView.rx.items是一个绑定器函数,用于处理元素的可观察序列,例如Observable 。 绑定创建一个ObserverType,将其自身订阅可观察的朋友数组。 它还将其自身设置为tableView的dataSourcedelegate 。 每当从friendCells接收到新值时,tableView都会重新加载其内容。

RxSwift调用我们为每个项目定义的闭包。 在这里我们可以配置单元。 元素包含在视图模型侧定义的枚举值,而index是元素的索引。 由于我们的视图只有一个部分,因此我们将使用部分值零将索引转换为indexPath。 然后,我们将使用switch来检查元素是否包含.empty.error.empty单元格。

在正常情况下,我们将从tableView中viewModel单元格viewModel接收到的viewModel设置为单元格viewModel

在错误情况下,我们将创建一个默认的UITableViewCell并将所提供的错误消息设置为textLabel?.text 。 在空白单元格的情况下,我们将与在错误情况下的情况相同,不同的是,我们将使用硬编码的“无可用数据”作为textLabel?.text

现在,我们已经处理了tableView的数据源和委派,剩下的就是确保在释放View时,使用disposeBag来处理此可观察对象。

所以你怎么看? 将这段代码与设置数据源和实现所有tableView委托函数的常规方法进行比较时,您觉得哪一个更容易?

现在,让我们看看如何通过检查单元格删除来处理单元格的选择!

处理单元格删除

单元格删除也由rx扩展提供的功能处理:

同样,我们可以使用.rx访问tableView的辅助函数。 每当为tableView调用delete事件时,也会调用modelDeleted 。 因此,在函数内部,我们只需检查单元格类型是否符合我们的期望,然后使用正确的ViewModel作为参数调用viewModel.delete函数。 由于好友应用程序通过从服务器重新加载内容来更新单元格,因此我们还将取消选择此处的行以使UI顺利工作。

选择一个单元格是通过modelSelected完成的,其处理与删除单元格非常接近。 我希望您仅通过查看代码就能自己弄清楚。

现在,在这部分中剩下的唯一事情就是提出错误和加载提示! 那不令人兴奋还是什么? 🙂

呈现错误并加载平视显示器

bindViewModel() ,我们还开始观察何时显示加载界面,并在需要时显示错误说明。 我们可以采用与从网络客户端接收朋友时监听可观察状态时相同的方式进行操作。 但是由于错误处理在这里并不那么复杂,因此我们可以通过以下更简单的方式来做到这一点:

首先,我们将获取onShowError并映射接收到的事件。 每当我们收到onNext事件时,我们都将使用$0访问发出的SingleButtonAlert值,并显示错误对话框。 接下来,我们将调用subscribe以开始侦听事件,最后,我们将设置disposeBag来处理可观察对象。 接下来,我们将对onShowLoadingHud做同样的事情。

至此,带有MVVM系列的RxSwift的第一部分完成了。 在下一部分中,我们将看到如何验证来自多个UITextView的输入数据,以及如何将数据提供回呈现的viewController。 我们还将检查如何在viewModel和View之间将数据绑定到来回的UIComponent。

如果您有任何问题,评论或反馈,请在下面发表评论或在Twitter上与我联系! 另外,如果您喜欢该帖子,希望与您的一些朋友分享,我将非常感谢! 感谢您的阅读,下次见,我的朋友!

-Jussi Suojanen

Swifty SW开发人员


查看更多:

英文:www.vincit.com

芬兰语:www.vincit.fi