如何使用RACCommands处理表数据请求

时间:2014-03-20 16:27:48

标签: reactive-cocoa

我有一个用于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,所以我很感激其他任何提示。

谢谢!

1 个答案:

答案 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行看到。
  • 第5行:您的代码有很多保留周期。您无法在self强烈引用的块中强烈引用self(直接或通过间接的对象所有权链)。 ReactiveCocoa附带@strongify/weakify宏来帮助减少这种麻烦。如您所见,只要您需要在命令所拥有的块中引用self(它本身由self拥有,因此循环),您需要创建一个弱引用自我然后在块内使用它。您可以在块内部使用@strongify来避免使用ARC的释放机制进行数据竞争。
  • 第6行:将didBecomeActive次通知转换为self.command的执行,参数为kReload(这将替换旧的reloadCommand),但仅限于self.data != nil,使用-filter:
  • 第12行:这是您实际调用[self.command execute:]的地方,但是您可以通过将选择器提升到上面创建的信号上来实现此目的。大概你有一些其他未示出的代码用于执行self.loadCommand,你需要调整它来代替@( kLoad )
  • 第13行:当您想要订阅信号而不实际使用其值时,通常会使用-publish/connect惯用法。这在RAC 2.x中并非绝对必要。
  • 第18行:您可以看到该命令现在将使用NSNumber调用其信号块,即您已提升到第6行创建的信号上的常量。但是,在哪里在命令返回数据之前,它现在返回一个包含-execute:输入和数据的元组。这是因为您将在下面看到,下游操作需要访问这两条信息。
  • 第23行:您现在可以直接在RAC()中使用信号订阅块(几乎总是反模式)中的-configureCommand宏。方法。这里我们说的是,无论何时执行命令,都要将self.page设置为0++self.page,具体取决于命令是使用kLoad还是{{1}执行的}。 kReload操作只是方便了解通过信号发送的元组,这里我们只关心常量。 请注意在此-reduceEach:操作中增加self.page的副作用。在信号操作中产生副作用通常是不好的形式。危险在于,如果-reduceEach:以外的其他内容订阅了返回的信号,则实例变量的增量可能比您预期的多。如果您能够以某种方式从数据源获取RAC(self, page)的值,而不是在全局实例变量中跟踪它,那将是理想的。
  • 第27行:这只是您在第23行所做内容的变化,但是您设置self.page而不是设置self.page。这次self.data操作确实需要使用元组中的第二个值(数据),该值适当地转换为-reduceEach:kLoad,然后最终设置在ivar上。