ReactiveCocoa MVVM与UITableView

时间:2014-04-21 18:28:40

标签: objective-c mvvm reactive-cocoa

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

我省略了UITableDataSource / Delegate方法,因为这些方法很简单。下面的代码说明了我如何设计ViewModel和ViewController以适应它们。

ViewModel.h

@property (strong, nonatomic, readonly) RACCommand *getItemsCommand;
@property (strong, nonatomic, readonly) NSArray *items;

ViewModel.m

- (instancetype)init {
    self = [super init];
    if (!self) return nil;

    @weakify(self);
    self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [[ItemsDataSource getItems]
                doNext:^(NSArray *items) {
                    @strongify(self);
                    // I actually do a little extra work here such as sorting
                    // the items appropriately for the view.
                    self.items = items;
                }];
    }];

    return self;
}

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.tableView addSubview:self.refreshControl];

    RACSignal *refreshSignals = [RACSignal merge:@[
        [self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged],
        [RACSignal return:nil]
    ]];

    [refreshSignals
     subscribeNext:^(id x) {
         [self.viewModel.getItemsCommand execute:nil];
     }];

    [RACObserve(self.viewModel, items)
     subscribeNext:^(NSArray *items) {
         [self.tableView reloadData];
     } completed:^{
         [self.refreshControl endRefreshing];
     }];
}

问题/问题

  1. 我呼叫completed的{​​{1}}区块永远不会被执行,而在我的生命中,我无法弄清楚原因。
  2. 使用公共方法endRefreshing而不是- (RACSignal *)getItems RACCommand会更好吗?
  3. 我在ViewModel中使用getItems是否正确,以便在不引起额外订阅的情况下应用副作用(即对items数组进行排序)?

2 个答案:

答案 0 :(得分:3)

我建议getItemsCommand使用-map:来排序和处理items数组。将任何其他副作用保留在单独的-doNext:中。一旦您的命令遵循此模式(在RAC中更具组合性),您就可以使用RAC()宏将命令的完成产品(已排序的数组)分配给items属性。

RAC(self, items) = [self.getItemsCommand.executionSignals concat];

RAC具有对UIRefreshControl的内置命令支持,它将启动/停止刷新控制以及命令的启动/停止。您应该会发现可以将UIRefreshControl代码缩减为:

self.refreshControl.rac_command = self.getItemsCommand;

对于表格重新加载,您可以执行以下操作:

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

希望有所帮助。

答案 1 :(得分:2)

1)好吧,让我们来看看信号:

RACObserve(self.viewModel, items)

什么时候完成?仅当self.viewModelself被取消分配时,就像其他任何RACObserve一样。只要这些对象存在,只要您设置next,它就会一直self.items

endRefreshing完成执行后,您似乎希望它getItemsCommand,并且您有这种隐含的期望,因为您知道命令集self.viewModel.items,所以完成将以某种方式传播 - 但事实并非如此。要查看命令何时完成,您必须订阅命令的返回信号。

2)RACCommand的优点是自动启用/禁用行为,您在这里没有真正利用它。更典型的事情是self.refreshControl.rac_command = self.viewModel.getItemsCommand;。那将为你处理endRefreshing的东西,为你节省第1部分的麻烦。

3)......有点儿。 do*系列方法为每次订阅信号注入了副作用。因此,如果您订阅了两次信号,并且它发送了下一个信号,则它将调用任何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;
    }];