是RACCommand新的最好的朋友?
的 RACCommand
是最重要的部分之一ReactiveCocoa最终可以节省你大量的时间和帮助使你的iOS或OS X应用程序更健壮。
我见过几个人新ReactiveCocoa(以下略RAC)不完全了解 RACCommand
工作时,它应该被使用。 所以我认为这将是有用的写一个小介绍了光。 官方文档不给很多如何使用RACCommand的例子,但是 评论在头文件是伟大的。 然而,他们可能很难理解如果你新RAC。
的 RACCommand
类是用来表示一些行动的执行。 经常引发的执行一些动作在UI中。 如当用户水龙头一个按钮。 RACCommand
可以配置为处理实例推理何时可以执行。 这可以很容易地绑定到UI和命令还将确保它不会开始执行时不启用。 常用的策略命令可以执行时离开 allowsConcurrentExecution
在它的默认值 没有
。 这将确保该命令不重新开始执行是否已经执行。 命令执行的结果是作为一个表示 RACSignal
并因此产生的结果 下一个:
(代表新值或结果), 完成
或 错误:
。 下面,我将展示如何使用这个。
示例应用程序
让我们假设我们是在做一项简单的iOS应用程序可以让用户订阅的电子邮件列表。 在最简单的形式,我们将有一个文本字段和一个按钮。 当用户进入了他的电子邮件和水龙头按钮应该张贴一些webservice电子邮件地址。 很容易。 但是有一些优势情况下,我们应该确保处理提供最好的体验。 如果用户水龙头按钮两次会发生什么? 错误是怎么处理的? 如果电子邮件是无效的? RACCommand
可以帮助我们处理这些案件。 我实现了一个小型应用程序来演示在这篇文章中讨论的概念。

这里的示例应用程序的源代码: 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
宏(中定义 libextobjc
pod)以避免保留周期。 所以更好的只是尽可能完全避免副作用与原始实现像我一样。
有一个重要的细节需要注意的 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:
方法将自动订阅原始信号和多播。 这基本上意味着你没有订阅返回的信号,但是如果你这样做你不应该害怕副作用发生两次。