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

这是“如何在MVVM中使用RxSwift”系列的第二篇文章。 在第一部分中,我们从Cocoapods设置了RxSwift,并检查了如何使用VariableObservablePublishSubject 。 这次,我们将创建一个视图,可用于创建和更新服务器好友。 在激活提交按钮之前,我们还将看到如何验证所有文本字段中的输入。 之后,我们将检查如何在视图模型和视图之间的UITextField来回绑定数据。

如果您不熟悉“朋友”应用程序,则可以使用该应用程序下载朋友列表并将其显示在表格视图中。 您还可以添加,删除和更新朋友。 我已经使用MVVM架构实现了该应用程序,当然,我使用Vapor快速编写了后端! 如果您想在没有RxSwift的情况下学习基本的MVVM,请查看我使用Swift应用程序的旧MVVM模式。

您可以从GitHub获取该应用程序的源代码,记住要签出RxSwift分支。 当然,您也可以在没有源代码的情况下关注此帖子。 但是现在,让我们开始吧!

因此,我们的目标是向服务器添加朋友信息,并更新该信息。 我们将使用相同的视图进行操作。

我们有一个视图,您可以用来输入朋友的名字,姓氏和电话号码。 首先,我们定义一个名为FriendViewModel的协议。 然后,我们将有两个符合该协议的视图模型: AddFriendViewModelUpdateFriendViewModel 。 在本教程中,我们将深入研究UpdateFriendViewModel 。 当我们打开编辑视图时,它执行与AddFriendViewModel相同的所有操作,并用朋友的信息填充文本字段。

与删除朋友的第一部分一样,还使用rx扩展中的功能设置了单元格点击。 现在,我们使用modelSelected函数并订阅其发出的事件。 首先,我们将检查单元格类型是否正常,并将viewModelif case let语法绑定。 然后,将视图模型存储到新的ReadOnce对象并执行所需的ReadOnce

ReadOnce是一个帮助程序类,可确保在打开视图时不使用旧的视图模型:

该值存储在私有var value 。 您只能使用read功能来访问它。 如果您已阅读一次,则isRead设置为false。 下次调用此方法时,它将返回一个nil值。 当我们进入一个新视图时,我们总是创建一个新的ReadOnce对象,这使它更加安全。 现在,将旧值弄乱新视图创建的机会很小。

然后,我们可以检查是否应该执行segue。 在shouldPerformSegue内部,我们可以检查isRead变量。 如果返回false,我们可以继续。

现在我们知道执行了segue,我们将在prepareForSegue设置视图模型,如下所示:

这样,我们将确保始终使用正确的信息打开视图控制器。

这里一件有趣的事是updateFriends观察器。 更新朋友后,当我们返回朋友列表时,我们要确保列表是最新的。 这就是为什么我们必须设置观察者以在必须从服务器更新好友时获取事件的原因。 我们还想确保此观察者已从内存中释放。 如果我们对从内存中释放的视图控制器具有活动的可观察订阅,则可能会出现问题。 我们可以确定在控制台上打印“ ONCOMPLETED”时我们已经取消了订阅。 我们将在FriendViewController代码中回到这个问题。 然后,我将向您展示如何防止内存问题。

现在我们知道了如何打开视图来编辑朋友。 接下来,让我们看看如何实现UpdateFriendViewModel

看一下下面的协议定义。

现在,我们已经熟悉所有变量定义,但让我们快速回顾一下。 我们有VariableObservablePublishSubject 。 我们记得,它们都是可观察的。 Observable发送OnNextOnErrorOnCompleted来更改状态。 通过Variable ,我们使用value属性更新其值,然后通过onNext事件将其发送给订阅者。 还保证Variable不会产生错误。 PublishSubject行为与Observable相同,但也可以订阅其他Observable

我知道这是对RxSwift可观察对象的快速回顾。 如果您想回顾一下如何订阅活动等,请查看该系列的第一篇文章。 现在,让我们进入UpdateFriendViewModel ,看看我们应该如何使用所有这些。

如前所述, UpdateFriendViewModel符合FriendViewModel 。 让我们首先检查变量的实现:

在顶部,我们有PublishSubject 。 它们发出事件以显示错误,并在用户点击“提交”按钮时导航回到朋友列表视图。 在这些之下,我们有很好的旧disposeBag。 然后,我们将所有朋友信息定义为Variables 。 我们还拥有视图的标题:“更新朋友”。 最后两个公共变量是onShowLoadingHudsubmitButtonEnabled

我们希望loadInProgress是一个私有变量。 这样,我们无法在视图控制器端更改状态。 相反,我们已将onShowLoadingHud定义为计算属性。 这是一个公共的观察结果,我们可以在视图控制器端使用它。 它以可观察的loadInProgress返回loadInProgress 。 我们在视图控制器端订阅了该可观察对象,并在其状态更改时得到通知。 distinctUntilChanged确保该值仅发送一次。

SubmitButtonEnabled有点有趣。 它结合了一些变量并验证了输入。 然后,它决定是否应启用该按钮。

为此,我们必须引入更多的私有变量:

用户可以编辑的每个输入都有一个计算变量。 所有变量都将原始变量作为可观察值返回。 例如:我们将firstnameValidfirstname绑定。 在asObservable ,我们使用map并检查变量中的值是否大于零个字符,并返回一个布尔值。

现在,让我们再次观察一下submitButtonEnabled 。 我们可以看到它正在观察我们定义的所有那些私有变量。 它采用发出的最新值并组合布尔值。 如果全部正确,则启用按钮,然后用户可以将信息更新到后端。

视图模型中的最后两个变量是:

AppServerClient是应用程序中所有网络的层。 当用户将朋友的信息更新到后端时,我们使用appServerClient 。 我们需要最后一个变量friendId来标识用户正在更新的朋友。

我们几乎完成了视图模型。 接下来,我们将进入构造函数并发送朋友信息。 让我们从构造函数开始:

在构造函数内部,我们首先设置从FriendCellViewModel获得的所有朋友信息。 然后我们将设置appServerClient 。 我们想在这里使用依赖注入来使类可测试。 最后,我们将设置submitButton 。 我们订阅onNext事件,并在用户单击按钮时调用submitFriend

提交朋友信息的过程如下:

首先,我们将loadInProgress设置为true以激活加载指示器。 接下来,我们将从AppServerClient调用patchFriend函数。 它以朋友信息作为参数,并返回我们可以订阅的Observable 。 当请求成功时,可观察对象将发出一个onNext事件。 然后,我们将隐藏加载指示器并发出onNavigateBack事件。 这样,我们将返回朋友列表视图。

如果出现问题,可观察对象将发出onError事件。 我们将隐藏加载指示器,并创建具有正确标题,消息和操作的SingleButtonAlert 。 由于警报是在按下按钮后自动解除的,因此此处唯一的操作是在控制台中打印文本以查看按钮是否起作用。 之后,我们将为onNext发出一个onNext事件以显示警报。 您可能想知道的一件事是getErrorMessage函数。 在UpdateFriendViewModel的底部,我们为PatchFriendFailureReason定义了一个私有扩展。 我们使用它将已知的错误消息转换为呈现给用户的文本:

它检查PatchFriendFailureReason枚举是否包含已知值,并返回我们可以在错误弹出窗口中显示的文本。

现在,我们将进入视图控制器端!

我们使用FriendViewController创建一个新朋友,并更新一个旧朋友。 在文件的顶部,我们为UI组件和视图模型等定义了熟悉的定义。

在这里,我们还看到了我们已经讨论过的updateFriends变量。 这样,我们将通知好友列表进行自我更新。 正如我们所讨论的,这可能会导致内存问题。 我们要确保在释放视图控制器时从内存中释放观察者。 我们可以通过在viewWillDisapper为观察者调用onCompleted来确保发生这种情况:

现在,让我们检查一下如何处理视图模型和控制器之间的绑定:

加载视图时,我们将调用bindViewModel函数。 在内部,我们将确保我们预订了所有更改UI状态的事件。 此外,我们还确保将所有值更新为用户可以在UI中更改的视图模型。 首先,我们将解开viewModel以摆脱所有可选的处理。 顺便说一句,这使代码更加清晰。 接下来,我们将为视图设置标题。 title = viewModel.title.value 。 然后,我们还有更多有趣的事情要做。

视图模型中的文本字段和好友值需要两种方式进行绑定。 打开视图控制器后,我们想用视图模型中的值填充表单中的文本字段。 另外,当用户更改这些值时,我们希望将新值更新为视图模型。 为此,我们创建一个私有函数: bind(textField: UITextField, to variable: Variable)

Variable是我们在视图模型方面拥有的变量。 我们将使用rx扩展中的text属性将该值绑定到文本字段。 这样,我们总是在第一次打开视图时更新文本字段。

通过textField ,我们还将使用rx扩展名中的text变量。 然后,我们将使用unwrap函数,因为text变量是可选的(以确保它实际上包含一个值)。 最后,我们将其绑定到视图模型的变量。 我们还将两个参数都添加到disposeBag ,以确保不会出现任何内存问题。

这是一个更清洁的解决方案。 与分别调用所有文本字段和分别查看模型变量相比,它节省了很多行。 我真的很喜欢,希望你也喜欢!

接下来,让我们激活并将“提交”按钮以及当前的加载状态和错误绑定到用户。

同样,我们使用rx扩展名。 我们将视图模型的submitButtonEnabled绑定到buttonSubmit ,还为视图模型的submitButtonTapped设置了一个轻submitButtonTapped处理程序。

接下来,我们将订阅onShowLoadingHud 。 同样,我们使用map从其发出的事件中获取布尔值。 并要求setLoadingHud呈现或隐藏加载状态。

onNavigateBack非常简单。 我们唯一需要记住的是发出updateFriends事件以更新朋友列表。 使用onShowError ,我们再次使用map获取布尔值。 然后,我们调用presentSingleButtonDialog来显示错误。

其余代码仅用于处理文本字段的激活。 如果您想知道它是如何完成的,请参考带有Swift应用程序第3部分的MVVM并搜索activeTextField

这就是我今天想和您一起经历的一切! 如前所述,我不会在这篇文章中介绍AddFriendViewModel 。 它与UpdateFriendViewModel非常相似,您可以自己完成。

通过本篇以及上一篇文章,我们现在已经了解了RxSwift的基础知识。 我们知道如何使用UITableView。 如何在视图模型和视图控制器之间来回处理可观察对象的订阅。 我们还对用户输入数据等进行了基本验证! 但是,我们仍有一些工作要做。 下次,我们将检查如何对应用程序进行单元测试!

希望您到目前为止喜欢该系列! 如果您有任何疑问或意见,请在Twitter上发表评论或给我发消息! 如果您喜欢该帖子,请传播这个词,并告诉您的朋友。 因此,直到下一次,祝我的朋友愉快!

-Jussi Suojanen

Swifty SW开发人员