Objective c 带有UITableView的反应式MVVM

Objective c 带有UITableView的反应式MVVM,objective-c,mvvm,reactive-cocoa,Objective C,Mvvm,Reactive Cocoa,我正在使用ReactiveCocoa并尝试应用MVVM。我有一个相当典型的UITableView场景,其中有一个用于重新加载数据的刷新控件 我省略了UITableDataSource/Delegate方法,因为它们是直接的。下面的代码说明了我是如何将ViewModel和ViewController设计到一起的 ViewModel.h ViewModel.m ViewController.m 问题 我调用endRefreshing的已完成块从未执行过,就我的一生而言,我不知道为什么。 使用公共方法

我正在使用ReactiveCocoa并尝试应用MVVM。我有一个相当典型的UITableView场景,其中有一个用于重新加载数据的刷新控件

我省略了UITableDataSource/Delegate方法,因为它们是直接的。下面的代码说明了我是如何将ViewModel和ViewController设计到一起的

ViewModel.h

ViewModel.m

ViewController.m

问题

我调用endRefreshing的已完成块从未执行过,就我的一生而言,我不知道为什么。 使用公共方法-RACSignal*getItems而不是getItems命令是否更好? 我在ViewModel中使用的doNext:是否正确,以便应用副作用,即在不引起额外订阅的情况下对items数组进行排序?
1那么我们来看看信号:

RACObserve(self.viewModel, items)
什么时候能完成?仅当self.viewModel或self被解除分配时,就像其他RACObserve一样。只要这些对象存在,它就会在您设置self.items时继续下一步

似乎您希望它在getItemsCommand完成执行后结束刷新,并且您有这样一种隐式的期望,因为您知道该命令设置self.viewModel.items,该完成将以某种方式传播,但事实并非如此。要查看命令何时完成,必须订阅命令返回的信号

2 RACCommand的优点是自动启用/禁用行为,您在这里并没有真正利用它。更规范的做法是self.refreshControl.rac_command=self.viewModel.getItemsCommand;。这将为您处理令人耳目一新的内容,让您免于第1部分中的头痛

3…有点。do*方法家族为信号的每个订阅注入副作用。因此,如果您订阅一个信号两次,并且它发送一个next,那么它拥有的任何doNext块都将被调用两次。显式订阅更清晰,因为无论订阅多少次,您都希望每次执行一次

    @weakify(self);
    self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        RACSignal *itemsSignal = [ItemsDataSource getItems];
        [itemsSignal subscribeNext:^(NSArray *items) {
            @strongify(self);
            // Do stuff...
            self.items = items;
        }];
        return itemsSignal;
    }];

1那么我们来看看信号:

RACObserve(self.viewModel, items)
什么时候能完成?仅当self.viewModel或self被解除分配时,就像其他RACObserve一样。只要这些对象存在,它就会在您设置self.items时继续下一步

似乎您希望它在getItemsCommand完成执行后结束刷新,并且您有这样一种隐式的期望,因为您知道该命令设置self.viewModel.items,该完成将以某种方式传播,但事实并非如此。要查看命令何时完成,必须订阅命令返回的信号

2 RACCommand的优点是自动启用/禁用行为,您在这里并没有真正利用它。更规范的做法是self.refreshControl.rac_command=self.viewModel.getItemsCommand;。这将为您处理令人耳目一新的内容,让您免于第1部分中的头痛

3…有点。do*方法家族为信号的每个订阅注入副作用。因此,如果您订阅一个信号两次,并且它发送一个next,那么它拥有的任何doNext块都将被调用两次。显式订阅更清晰,因为无论订阅多少次,您都希望每次执行一次

    @weakify(self);
    self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        RACSignal *itemsSignal = [ItemsDataSource getItems];
        [itemsSignal subscribeNext:^(NSArray *items) {
            @strongify(self);
            // Do stuff...
            self.items = items;
        }];
        return itemsSignal;
    }];
我建议让getItemsCommand使用-map:对items数组进行排序和处理。将任何其他副作用工作留在单独的-doNext:中完成。命令遵循此模式(在RAC中更具组合性)后,可以使用RAC宏将命令的成品(排序数组)指定给items属性

RAC(self, items) = [self.getItemsCommand.executionSignals concat];
RAC具有对UIRefreshControl的内置命令支持,它将在启动/停止命令的同时启动/停止刷新控件。您应该发现,您可以将UIRefreshControl代码缩减为:

要重新加载表,可以执行以下操作:

[RACObserve(self, items) subscribeNext:^(id _) {
    @strongify(self);
    [self.tableView reloadData];
}];
希望能有所帮助。

我建议使用getItemsCommand和-map:对items数组进行排序和处理。将任何其他副作用工作留在单独的-doNext:中完成。命令遵循此模式(在RAC中更具组合性)后,可以使用RAC宏将命令的成品(排序数组)指定给items属性

RAC(self, items) = [self.getItemsCommand.executionSignals concat];
RAC具有对UIRefreshControl的内置命令支持,它将在启动/停止命令的同时启动/停止刷新控件。您应该发现,您可以将UIRefreshControl代码缩减为:

要重新加载表,可以执行以下操作:

[RACObserve(self, items) subscribeNext:^(id _) {
    @strongify(self);
    [self.tableView reloadData];
}];

希望这能有所帮助。

非常感谢您提供如此详细和书面的回复。这为我澄清了几件事。使用self.refreshControl.rac_command=self.viewModel.getItemsCommand非常好,但是我确实失去了在加载时执行命令的能力。在viewDidLoad中调用[self.viewModel.getItemsCommand]并执行:nil]可以工作,但感觉
这就好像我正在突破FRP范式。有没有更好的方法来实现这一点?我不知道有没有更干净的方法。RACCommand已经处于反应式和命令式之间的一个奇怪的空间。。。除了通过execute:方法之外,没有办法说此时应该执行此操作。你可以用提升来让它看起来更实用,但偶尔做一些必要的事情是一种生活方式。好吧,这不是问题,有道理。毕竟,我们使用的是命令式语言的扩展,而不是纯粹的函数式语言。在采纳了你的建议后,一切都很顺利。再次感谢@在信号块内执行订阅将导致两个信号订阅,可能不是您想要的。@tragicsans调用-execute:显式是完全可以的,这就是为什么它是公共的。如果有帮助的话,你可以把RACCommand想象成一个函数,而-execute:是你调用函数的方式。非常感谢你提供了如此详细和良好的响应。这为我澄清了几件事。使用self.refreshControl.rac_command=self.viewModel.getItemsCommand非常好,但是我确实失去了在加载时执行命令的能力。在viewDidLoad中调用[self.viewModel.getItemsCommand和execute:nil]是可行的,但感觉好像我正在打破FRP范式。有没有更好的方法来实现这一点?我不知道有没有更干净的方法。RACCommand已经处于反应式和命令式之间的一个奇怪的空间。。。除了通过execute:方法之外,没有办法说此时应该执行此操作。你可以用提升来让它看起来更实用,但偶尔做一些必要的事情是一种生活方式。好吧,这不是问题,有道理。毕竟,我们使用的是命令式语言的扩展,而不是纯粹的函数式语言。在采纳了你的建议后,一切都很顺利。再次感谢@在信号块内执行订阅将导致两个信号订阅,可能不是您想要的。@tragicsans调用-execute:显式是完全可以的,这就是为什么它是公共的。如果有帮助的话,你可以把RACCommand想象成一个函数,而-execute:就是你调用函数的方式。谢谢Dave,这很有意义。我已经成功地消除了命令信号块的所有副作用。工作很有魅力。谢谢戴夫,这很有道理。我已经成功地消除了命令信号块的所有副作用。工作得很有魅力。