我有一个用于UITableView的ViewModel,它包含一组数据元素。 UITableView实现了pull-to-refresh和无限滚动行为。通过RestKit从服务器以分页方式请求数据元素,这意味着我必须以某种方式跟踪当前页面。我创建了两个单独的RACCommands来区分刷新和无限滚动加载,其中刷新总是加载第0页。整个过程有效,但我不喜欢它,并想知道是否有更好的方法来执行此操作。此外,现在可以同时执行2个命令,这不是预期的。
@interface TableDataViewModel ()
@property(nonatomic, readonly) RestApiConnector *rest;
@property(nonatomic) int page;
@property(nonatomic) NSMutableArray *data;
@property(nonatomic) RACCommand *loadCommand;
@property(nonatomic) RACCommand *reloadCommand;
@end
@implementation TableDataViewModel
objection_requires_sel(@selector(rest))
- (id)init {
self = [super init];
if (self) {
[self configureLoadCommands];
[self configureActiveSignal];
}
return self;
}
- (void)configureActiveSignal {
[self.didBecomeActiveSignal subscribeNext:^(id x) {
if (!self.data) {
[self.reloadCommand execute:self];
}
}];
}
- (void)configureLoadCommands {
self.loadCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [self.rest dataSignalWithPage:self.page];
}];
self.reloadCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [self.rest dataSignalWithPage:self.page];
}];
self.loadCommand.allowsConcurrentExecution = FALSE;
self.reloadCommand.allowsConcurrentExecution = FALSE;
[self.reloadCommand.executionSignals subscribeNext:^(id x) {
RAC(self, data) = [x map:^id(id value) {
self.page = 0;
return [value mutableCopy];
}];
}];
[self.loadCommand.executionSignals subscribeNext:^(id x) {
RAC(self, data) = [x map:^id(id value) {
self.page++;
if (self.data) {
NSMutableArray *array = [self.data mutableCopy];
[array addObjectsFromArray:value];
return array;
} else {
return [value mutableCopy];
}
}];
}];
}
@end
我刚刚开始使用ReactiveCocoa,所以我很感激其他任何提示。
谢谢!
答案 0 :(得分:3)
我刚开始使用ReactiveCocoa,所以我很感激其他任何提示。
如果您一次只需要一个命令或另一个命令,那么实现此目的的一种方法是使用单个命令(并禁用并发执行,如您所做的那样):
1 const NSUInteger kLoad = 0;
2 const NSUInteger kReload = 1;
3
4 - (void)configureActiveSignal {
5 @weakify(self);
6 RACSignal *dba = [[self.didBecomeActiveSignal filter:^(id _) {
7 @strongify(self);
8 return self.data;
9 }]
10 mapReplace:@( kReload )];
11
12 RACSignal *ls = [self.command rac_liftSelector:@selector(execute:) withSignals:dba];
13 [[ls publish] connect];
14 }
15
16 - (void)configureCommand {
17 @weakify(self);
18 self.command = [[RACCommand alloc] initWithSignalBlock:^(NSNumber *loadOrReload) {
19 @strongify(self);
20 return RACTuplePack(loadOrReload, [self.rest dataSignalWithPage:self.page]);
21 }];
22
23 RAC(self, page) = [self.command.executionSignals reduceEach:^(NSNumber *loadOrReload, id _) {
24 @strongify(self);
25 return kReload == loadOrReload.integerValue ? @0 : @( ++self.page );
26 }];
27 RAC(self, data) = self.command.executionSignals reduceEach:^(NSNumber *loadOrReload, NSArray *data) {
28 @strongify(self);
29 if (kReload == loadOrReload.integerValue)
30 {
31 return [data mutableCopy];
32 }
33 else
34 {
35 NSMutableArray *ma = [self.data mutableCopy];
36 [ma addObjectsFromArray:data]
37 return ma;
38 }
39 }];
40
41 self.command.allowsConcurrentExecution = NO;
Nota bene,这完全没有经过考验。但我希望它传达了基本的想法。
command
。现在使用常量调用它来表示是否应该加载或重载,而不是使用数据数组进行调用,这可以在第18行看到。self
强烈引用的块中强烈引用self
(直接或通过间接的对象所有权链)。 ReactiveCocoa附带@strongify/weakify
宏来帮助减少这种麻烦。如您所见,只要您需要在命令所拥有的块中引用self
(它本身由self
拥有,因此循环),您需要创建一个弱引用自我然后在块内使用它。您可以在块内部使用@strongify
来避免使用ARC的释放机制进行数据竞争。didBecomeActive
次通知转换为self.command
的执行,参数为kReload
(这将替换旧的reloadCommand
),但仅限于self.data != nil
,使用-filter:
。[self.command execute:]
的地方,但是您可以通过将选择器提升到上面创建的信号上来实现此目的。大概你有一些其他未示出的代码用于执行self.loadCommand
,你需要调整它来代替@( kLoad )
。-publish/connect
惯用法。这在RAC 2.x中并非绝对必要。-execute:
输入和数据的元组。这是因为您将在下面看到,下游操作需要访问这两条信息。RAC()
中使用信号订阅块(几乎总是反模式)中的-configureCommand
宏。方法。这里我们说的是,无论何时执行命令,都要将self.page
设置为0
或++self.page
,具体取决于命令是使用kLoad
还是{{1}执行的}。 kReload
操作只是方便了解通过信号发送的元组,这里我们只关心常量。 请注意在此-reduceEach:
操作中增加self.page
的副作用。在信号操作中产生副作用通常是不好的形式。危险在于,如果-reduceEach:
以外的其他内容订阅了返回的信号,则实例变量的增量可能比您预期的多。如果您能够以某种方式从数据源获取RAC(self, page)
的值,而不是在全局实例变量中跟踪它,那将是理想的。self.page
而不是设置self.page
。这次self.data
操作确实需要使用元组中的第二个值(数据),该值适当地转换为-reduceEach:
或kLoad
,然后最终设置在ivar上。