ReactiveCocoa要点:理解和使用RACCommand

这篇文章附带的源代码在github:  https://github.com/olegam/RACCommandExample

是RACCommand新的最好的朋友?

的 RACCommand是最重要的部分之一ReactiveCocoa最终可以节省你大量的时间和帮助使你的iOS或OS X应用程序更健壮。

我见过几个人新ReactiveCocoa(以下略RAC)不完全了解 RACCommand工作时,它应该被使用。 所以我认为这将是有用的写一个小介绍了光。 官方文档不给很多如何使用RACCommand的例子,但是 评论在头文件是伟大的。 然而,他们可能很难理解如果你新RAC。

的 RACCommand类是用来表示一些行动的执行。 经常引发的执行一些动作在UI中。 如当用户水龙头一个按钮。 RACCommand可以配置为处理实例推理何时可以执行。 这可以很容易地绑定到UI和命令还将确保它不会开始执行时不启用。 常用的策略命令可以执行时离开 allowsConcurrentExecution在它的默认值 没有。 这将确保该命令不重新开始执行是否已经执行。 命令执行的结果是作为一个表示 RACSignal并因此产生的结果 下一个:(代表新值或结果), 完成或 错误:。 下面,我将展示如何使用这个。

示例应用程序

让我们假设我们是在做一项简单的iOS应用程序可以让用户订阅的电子邮件列表。 在最简单的形式,我们将有一个文本字段和一个按钮。 当用户进入了他的电子邮件和水龙头按钮应该张贴一些webservice电子邮件地址。 很容易。 但是有一些优势情况下,我们应该确保处理提供最好的体验。 如果用户水龙头按钮两次会发生什么? 错误是怎么处理的? 如果电子邮件是无效的? RACCommand可以帮助我们处理这些案件。 我实现了一个小型应用程序来演示在这篇文章中讨论的概念。

Screenshot of example app

这里的示例应用程序的源代码: https://github.com/olegam/RACCommandExample

用一个非常简单的视图控制器应用还演示了一种实践MVVM模式iOS应用程序。 基本视图控制器设置视图层次和实例化视图模型的一个实例。

- (void)bindWithViewModel {
  RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
  self.subscribeButton.rac_command = self.viewModel.subscribeCommand;
  RAC(self.statusLabel, text) = RACObserve(self.viewModel, statusMessage);
}

上面的方法(称为 viewDidLoad)创建视图和视图模型之间的绑定。 所有有趣的东西在视图模型。 它具有以下界面:

@interface SubscribeViewModel : NSObject

@property(nonatomic, strong) RACCommand *subscribeCommand;

// write to this property
@property(nonatomic, strong) NSString *email;

// read from this property
@property(nonatomic, strong) NSString *statusMessage;

@end

RACCommand属性暴露这是这篇文章的其余部分将是什么。 两个其他的字符串属性绑定到如上所示的属性视图。 全面实施视图模型如下所示:

#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import "NSString+EmailAdditions.h"

static NSString *const kSubscribeURL = @"http://reactivetest.apiary.io/subscribers";

@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal *emailValidSignal;
@end

@implementation SubscribeViewModel

- (id)init {
  self = [super init];
  if (self) {
      [self mapSubscribeCommandStateToStatusMessage];
  }
  return self;
}

- (void)mapSubscribeCommandStateToStatusMessage {
  RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
      return NSLocalizedString(@"Sending request...", nil);
  }];

  RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
      return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
          return event.eventType == RACEventTypeCompleted;
      }] map:^id(id value) {
          return NSLocalizedString(@"Thanks", nil);
      }];
  }];

  RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
      return NSLocalizedString(@"Error :(", nil);
  }];

  RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
}

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}

+ (RACSignal *)postEmail:(NSString *)email {
  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  manager.requestSerializer = [AFJSONRequestSerializer new];
  NSDictionary *body = @{@"email": email ?: @""};
  return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}

- (RACSignal *)emailValidSignal {
  if (!_emailValidSignal) {
      _emailValidSignal = [RACObserve(self, email) map:^id(NSString *email) {
          return @([email isValidEmail]);
      }];
  }
  return _emailValidSignal;
}

@end

这可能看起来像一大块让我们通过更小的部分。 的 RACCommand我们最感兴趣的是这样的:

- (RACCommand *)subscribeCommand {
  if (!_subscribeCommand) {
      NSString *email = self.email;
      _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
          return [SubscribeViewModel postEmail:email];
      }];
  }
  return _subscribeCommand;
}

命令初始化的 enabledSignal参数。 这是一个信号指示时,可以执行的命令。 在我们的例子中它应该被允许执行时,用户输入的电子邮件地址是有效的。 的 self.emailValidSignal是一个信号,发送一个 没有或者一个 是的每次邮件更改。

的 signalBlock每次调用参数时需要执行的命令。 块应该返回一个信号代表执行。 自从我们离开的默认属性 allowsConcurrentExecution在 没有命令将看这个信号,不允许任何新的死刑执行之前完成进度。

因为命令按钮的设置 rac_command属性中定义的 UIButtton + RACCommandSupport类别按钮将自动启用和禁用状态之间变化时基于命令可以执行。

还命令按钮时将自动执行用户了。 我们免费得到这一切 RACCommand。 如果你需要手动执行命令你可以通过消息传递 ——[RACCommand执行:]。 参数是一个可选的输入。 我们这里不使用它,但它通常是非常有用的(按钮发送本身作为输入时调用 -execute:)。 的 -execute:的方法也是一个地方你可以把看执行的状态。 你可能是这样的:

[[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{
  NSLog(@"The command executed");
}];

在我们的示例中按钮执行命令(所以我们别叫 -execute:),因此我们必须倾听另一个属性的命令来更新UI时执行的命令。 有几个机会之间的选择。 这也许会让人有些迷惑。 的 executionSignals的属性 RACCommand是一个信号,发送一个 下一个:每一次的命令开始执行。 参数是信号产生的命令。 所以这是一个信号的信号。 我们使用的 mapSubscribeCommandStateToStatusMessage视图模型的方法得到一个信号与一个字符串值每次启动命令:

RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
  return NSLocalizedString(@"Sending request...", nil);
}];

得到类似的信号与一个字符串命令完成每次执行我们必须做更多的工作,如果我们想要纯粹的功能:

RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
  return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
      return event.eventType == RACEventTypeCompleted;
  }] map:^id(id value) {
      return NSLocalizedString(@"Thanks", nil);
  }];
}];

的 flattenMap:方法调用的块 subscribeSignal当命令执行。 这个块返回一个新的信号和它的值传递到由此产生的信号。 的 实现操作符让我们得到的信号 RACEvent(即。 的 下一个: 完整的和 错误:消息传递 RACEvent作为实例 下一个:值产生的信号)。 我们可以过滤这些事件只会从当信号完成的,在这种情况下将其映射到一个字符串值。 我松了你吗? 我希望不是这样,但您可能需要查找的文档 flattenMap:和 实现为了更好地理解他们所做的事情。

我们可以用不同的方式来实现这一行为,更少的功能,但可能更容易理解:

@weakify(self);
[self.subscribeCommand.executionSignals subscribeNext:^(RACSignal *subscribeSignal) {
  [subscribeSignal subscribeCompleted:^{
      @strongify(self);
      self.statusMessage = @"Thanks";
  }];
}];

然而,我不喜欢上面的实现,因为它涉及到副作用的街区。 这也有提到的缺点和保留 自我块。 因此我必须使用 @weakify和 @strongify宏(中定义 libextobjcpod)以避免保留周期。 所以更好的只是尽可能完全避免副作用与原始实现像我一样。

有一个重要的细节需要注意的 executionSignals财产。 这里的信号不包括错误事件。 对于那些有一个特别的 错误财产。 一个信号,发送在执行命令的任何错误 下一个:。 不像定期发送的错误 错误:事件,终止信号。 我们可以很容易的错误映射到字符串消息:

RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
  return NSLocalizedString(@"Error :(", nil);
}];

现在,当我们有3个信号状态信息我们想展示给用户可以将它们合并成一个信号和绑定 statusMessage(绑定到属性的视图模型 statusLabel.text属性的视图控制器)。

RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];

这是如何的一个例子 RACCommand可用于实践的iOS应用程序。我认为这种方式实现逻辑有很多优势在许多人会实现它的路吗 UITextFieldDelegate在视图控制器和大量的状态存储在实例变量或属性。

感兴趣的其他RACCommand细节

RACCommand有一个 执行属性,实际上是一个信号发送 是的当 执行:调用和 没有当它终止。 在订阅的信号将发送它的当前值。 如果你只需要得到的当前值和不需要一个信号,你可以立即:

BOOL commandIsExecuting = [[command.executing first] boolValue];

的 启用房地产也是一个信号发送 是的和 没有。 它将发送 没有当命令创建了一个 enabledSignal,发送一个信号 没有或者是执行和信号 allowsConcurrentExecutions被设置为 没有

如果你想消息 -execute:在不启用一个信号,表明它将立即发送一个错误,但错误不会被发送到 错误信号。

的 -execute:方法将自动订阅原始信号和多播。 这基本上意味着你没有订阅返回的信号,但是如果你这样做你不应该害怕副作用发生两次。

12月5日 th ,2013年  ReactiveCocoa


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值