I'm using ReactiveCocoa and am trying to apply MVVM. I have a fairly typical UITableView scenario with a refresh control for reloading data.
I've omitted the the UITableDataSource/Delegate methods as these are straight forward. The code below illustrates how I've designed the ViewModel and the ViewController to fit together.
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];
}];
}
Questions/Problems
completed
block where I call endRefreshing
never gets executed and for the life of me I can't figure out why.- (RACSignal *)getItems
instead of the getItems
RACCommand?doNext:
in the ViewModel correct in order to apply side effects (i.e. the sorting of the items array) without causing an additional subscription?1) Well, let's look at the signal:
RACObserve(self.viewModel, items)
When will that complete? Only when self.viewModel
or self
is deallocated, just like any other RACObserve
. As long as those objects are around, it'll keep on next
ing any time you set self.items
.
It appears that you want it to endRefreshing
once the getItemsCommand
finishes executing, and you have this sort of implicit expectation that, since you know that command sets self.viewModel.items
, that completion will somehow propagate -- but this isn't the case. To see when the command completes, you have to subscribe to the command's returned signal.
2) The advantage of RACCommand
is the auto enabling/disabling behavior, which you aren't really taking advantage of here. The more canonical thing to do would be self.refreshControl.rac_command = self.viewModel.getItemsCommand;
. That'll handle the endRefreshing
stuff for you, saving you the headache from part 1.
3) ...sort of. The do*
family of methods injects side effects for every subscription to the signal. So if you subscribe to a signal twice, and it sends a next, any doNext
block it has will be invoked twice. An explicit subscription is more clear, since you want to execute this exactly once per next, regardless of how many times it's subscribed to.
@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;
}];