ReactiveCocoa基本组件:深入浅出RACCommand
来源:互联网 发布:天猫和淘宝质量一样吗 编辑:程序博客网 时间:2024/06/04 23:27
什么是RACCommand?
RACCommand
是ReactiveCocoa框架中一个很重要的部分,如果利用好了RACCommand
,它将节省你很多的开发时间,并且让我们的应用更加健壮。
笔者遇到了很多刚接触ReactiveCocoa的开发者,他们一开始并不清楚RACCommand
是如何工作的,并且不知道该怎么使用它。所以写一篇关于RACCommand
简短的介绍会对大家有一点点帮助,在RAC的官方文档中,并没有太多提及RACCommand
的用法,虽然头文件中的注释是一份不错的材料,但是毕竟还是对新手有一点吃力。
RACCommand
代表着与交互后即将执行的一段流程。通常这个交互是UI层级的,比如你点击个Button。RACCommand
可以方便的将Button与enable状态进行绑定,也就是当enable为NO的时候,这个RACCommand
将不会执行。RACCommand
还有一个常见的策略:allowsConcurrentExecution
,默认为NO,也就是是当你这个command正在执行的话,你多次点击Button是没有用的。创建一个RACCommand
的返回值是一个Signal,这个Signal会返回next或者complete或者error。接下来我们来看一个范例。
RACCommand范例
接下来我们将实现一个邮箱订阅的功能,只有一个输入框和一个订阅按钮,当用户在输入框输入正确的邮箱,点击订阅将向服务器发送订阅的邮箱号。虽然看起来是一个很简单的需求,但是我们需要处理的细节还是挺多的,比如用户快速的点击了两次订阅按钮、还有如何捕捉订阅失败、如果这个邮箱是非法的怎么办?如果我们用RACCommand
来处理的话,其实是非常方便的。
另一方面,ReactiveCocoa也是实现iOS中MVVM模式的好框架。因此,在controller中我们来绑定view model。
1234567
- (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
方法中调用上面的方法,来进行view和view model的绑定。精华部分的实现都放在了view model中,我们先来看看view model的头文件:
1234567891011
@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
其中两个属性已经被我们在controller中绑定了,剩下的subcribeCommand是我们接下来要重点讲解的,view model的实现文件如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
#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
最有趣的是创建的时候:
123456789
- (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
参数,这个参数决定了command什么时候可以执行。在这个范例中,表示的是当我们输入的邮箱地址合法的时候才能执行。self.emailValidSignal
是一个返回YES或者NO的Signal。
signalBlock
参数将在每次我们需要执行command的时候调用,这个block返回一个Signal,这个Signal代表了之前所说的执行流程。我们之前保持了默认的allowsConcurrentExecution
属性为NO,这就保证了我们在完成执行block之前不会再次执行这个block。
因为在ReactiveCocoa中,UIButton的属性rac_command
定义在了一个UIButtton+RACCommandSupport
类别,UIButton的enable状态是与command的执行过程相关联绑定的。
因此当按钮被点击的时候command将会自动执行。当然,这些都是RACCommand
帮我们自动完成的。当你需要手动调用这个command的时候,可以调用-[RACCommand execute:]
方法,传入的参数是可选的,我们在这个例子中将传入nil(其实是button把自己当做参数传入了-execute:
方法),另外,这个方法也是一个监视执行流程的一个好地方,比如我们可以这么做:
123
[[self.viewModel.subscribeCommand execute:nil] subscribeCompleted:^{ NSLog(@"The command executed");}];
在我们的范例中,按钮自动为我们做了这个操作,我们没有调用-execute:
,因此当command执行的时候我们得从其他的属性来获得执行的状态,以便于我们更新UI。executionSignals
是RACCommand
的一个Signal属性,当每次command开始执行的时候,这个Signal就会发送next:
,next:
发送的参数就是初始化RACCommand
时signalBlock
,也就是说:它是Signal中的Signal。接下来在mapSubscribeCommandStateToStatusMessage
方法中我们初始化一个Signal来表示每当command开始执行的时候返回一个表示开始的字符串:
123
RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) { return NSLocalizedString(@"Sending request...", nil);}];
然后再实现一个类似的Signal来表示每当command执行完毕时候转换返回一个字符串:
1234567
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
方法返回一个新的Signal,并且这个新的Signal它的返回值将传递到最终的Signal中,materialize
操作符允许我们将Signal转换成RACEvent
,接下来我们就可以过滤这些事件,只允许成功事件通过,并且将成功事件转换成一个代表成功的字符串。如果大家对这步有不清楚的地方,可以去看看官方flattemMap和materialize的用法。
其实我们还可以换一个更简单的方式来实现:
1234567
@weakify(self);[self.subscribeCommand.executionSignals subscribeNext:^(RACSignal *subscribeSignal) { [subscribeSignal subscribeCompleted:^{ @strongify(self); self.statusMessage = @"Thanks"; }];}];
然而,我并不喜欢上面的实现方式,不仅是因为有副作用,而且对self的引用也很不方便,我们不得不使用@weakify
和@strongify
来避免循环引用。
这儿还有一个关于executionSignals
属性比较重要的知识点,它并不包含error事件,因此有一个专门的errors
属性的Signal,这个Signal会在执行command的任何阶段调用next:
发送错误信息,它并不会发送error:
,因为error:
会终止信号。因此我们可以轻松的转换这个错误信息:
123
RACSignal *failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) { return NSLocalizedString(@"Error :(", nil);}];
到现在为止,我们已经有了三个带有返回信息的Signal,因此我们将它们合并到一个新的Signal,并且绑定到view model的statusMessage
属性:
1
RAC(self, statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
到这儿,整个RACCommand
的流程就差不多结束了,我认为这种实现方式有很多的优势比起在view controller中使用UITextFieldDelegate
和保存过多的变量或属性。
关于RACCommand的其他兴趣点
RACCommand
有一个executing
Signal属性,当execute:
调用的时候它会发送YES,而当command终止的时候它会发送NO。如果你只是想得到当前的值可以这么做:
1
BOOL commandIsExecuting = [[command.executing first] boolValue];
如果你在command enabled状态为NO的时候手动调用了-execute:
,那么它会立刻发送一个错误,但是这个错误并不会发送到errors
Signal。
-execute:
方法会自动订阅Signal并且多播它,也就是说你不用订阅返回的Signal,但是如果你订阅的话也不用担心会产生副作用,也就是执行两次。
- ReactiveCocoa基本组件:深入浅出RACCommand
- ReactiveCocoa基本组件:理解和使用RACCommand
- ReactiveCocoa基本组件:理解和使用RACCommand
- ReactiveCocoa基本组件:理解和使用RACCommand
- ReactiveCocoa基本组件:理解和使用RACCommand
- ReactiveCocoa之RACCommand使用(五)
- ReactiveCocoa之RACCommand使用(五)
- ReactiveCocoa:理解和使用RACCommand
- ReactiveCocoa要点:理解和使用RACCommand
- ReactiveCocoa Essentials: Understanding and Using RACCommand
- ReactiveCocoa的基本使用方法
- ReactiveCocoa 基本用法
- 图解ReactiveCocoa基本函数
- ReactiveCocoa 基本用法
- ReactiveCocoa基本使用详解
- ReactiveCocoa 基本使用回忆录
- ReactiveCocoa 基本工作流程整理
- RACCommand 粗解
- 黑马程序员--多态
- flex两种方式画出一个动态时钟
- Java中静态变量的适用场景
- cocos2d-x v2 升级到 v3
- 从超算谈起
- ReactiveCocoa基本组件:深入浅出RACCommand
- MBProgressHUD的使用
- js 未结束的字符串常量错误解决方法
- java 调用.net webservice 带soap验证头消息方式
- find,find_if,find_end函数
- Thymeleaf基本知识
- 一种Dump文件解析
- Ajax常见问题
- 惨痛教训其二