RACCommand 是 RAC 中的最复杂的一个类之一,它也是一种广义上的信号,RAC 中信号其实是一种对象(或者是不同代码块)之间通信机制,在面向对象中,类之间的通信方式主要是方法调用,而信号也是一种调用,只不过它是函数式的,因此信号不仅仅可以在对象之间相互调用(传参),也可以在不同代码块(block)之间进行调用。
一般来说,RAC 中用 RACSignal 来代表信号。一个对象创建 RACSignal 信号,创建信号时会包含一个 block,这个 block 的作用是发送信号给订阅者(类似方法返回值或回调函数)。另一个对象(或同一个对象)可以用这个信号进行订阅,从而获得发送者发送的数据。这个过程和方法调用一样,信号相当于暴露给其它对象的方法,订阅者订阅信号相当于调用信号中的方法(block),只不过返回值的获得变成了通过 block 来获得。此外,你无法直接向 RACSignal 传递参数,要向信号传递参数,需要提供一个方法,将要传递的参数作为方法参数,创建一个信号,通过 block 的捕获局部变量方式将参数捕获到信号的 block 中。
而 RACCommand 不同,RACCommand 的订阅不使用 subscribeNext 方法而是用 execute: 方法。而且 RACCommand 可以在订阅/执行(即 excute:方法)时传递参数。因此当需要向信号传递参数的时候,RACComand 更好用。
此外,RACCommand 包含了一个 executionSignal 的信号,这个信号是对用户透明的,它是自动创建的,由 RACCommand 进行管理。许多资料中把它称之为信号中的信号,是因为这个信号会发送其它信号——即 RACCommand 在初始化的 signalBlock 中创建(return)的信号。这个信号是 RACCommand 创建时由我们创建的,一般是用于处理一些异步操作,比如网络请求等。
先来看看RACCommand 基础写法
/**
Command翻译过来就是命令
*/
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
NSLog(@"执行1");
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"执行3");
[subscriber sendNext:input];
return nil;
}];
}];
/**
executionSignals就是用来发送信号的信号源,
需要注意的是这个方法一定要在执行execute方法之前,否则就不起作用了,
*/
[command.executionSignals subscribeNext:^(id _Nullable x) {
[x subscribeNext:^(id _Nullable x) {
NSLog(@"执行4");
NSLog(@"接收数据%@",x);
}];
NSLog(@"执行2");
}];
[command execute:@"发送消息"];
整体看下来,发现,它与单纯的RACSignal是有一些区别的,从代码上我们直观的可以发现,RACCommand 执行顺序
1).RACCommand通过execute发送一个参数;
2).这时候会执行 initWithSignalBlock块,这个内部将创建并返回一个信号,RACCommand 通过executionSignals(也是一个信号) 找到返回信号并发送信号
3).信号内部进行发送消息.
4).订阅信号,接受信息.
通过这个特性,我们可以做一个简单的Demo。
项目最终实现目的,视图上 一个按钮 一个TextView, 当我们点击这个按钮的时候,会刷新TextView上的文字.
项目文件构成分成两个文件 一个ViewController控制器,另一个是ViewModel
代码:
ViewModel.h :
typedef NS_ENUM(NSUInteger, HTTPRequestStatus) {
HTTPRequestStatusBegin,
HTTPRequestStatusEnd,
HTTPRequestStatusError,
};
@interface RACCommandViewModel : NSObject
@property (nonatomic, assign, readwrite) HTTPRequestStatus status;
@property (nonatomic, strong, readwrite) RACCommand *requestData;
@property (nonatomic, strong, readwrite, nullable) NSDictionary *data;
@property (nonatomic, strong, readwrite, nullable) NSError *error;
@end
ViewModel.m :
- (instancetype)init {
self = [super init];
if (self) {
[self subscriberConmandSignals];
}
return self;
}
/**
RACCommand 中封装了各种信号,我们只用到了外层信号(executionSignal)和内层信号。订阅这些信号能够让我们实现两个目的:拿到请求返回的数据、跟踪 RACCommand 开始结束状态。定义一个方法来做这些事情:
*/
/**
订阅外层信号(即 executionSignals)。外层信号在订阅或执行(即 execute: )时发送。因此我们可以将它视作请求即将开始之前的信号,在这里将 self.error 清空,将 requestStatus 修改为 begin。
订阅内层信号,因为内层信号由外层信号(executionSignals)作为数据发送(sendNext:),而发送的数据一般是作为 subcribeNext:时的 block 的参数来接收的,因此在这个块中,块的参数就是内层信号。这样我们就可以订阅内层信号了,同时获取数据(保存到 data 属性)并修改 requestStatus 为 end。
RACCommand 比较特殊的一点是 error 信号需要在 errors 中订阅,而不能在 executionSignals 中订阅。在这里我们订阅了 errors 信号,并修改 data、error 和 requestStatus 属性值。
*/
- (void)subscriberConmandSignals {
@weakify(self)
//1. 订阅外层信号
[self.requestData.executionSignals subscribeNext:^(RACSignal* innerSignal) {
@strongify(self)
// 2. 订阅内层信号
[innerSignal subscribeNext:^(NSDictionary *x){
self.data = x;
self.status = HTTPRequestStatusEnd;
NSLog(@"11111111");
}];
self.error = nil;
self.status = HTTPRequestStatusBegin ;
}];
// 3. 订阅 errors 信号
[self.requestData.errors subscribeNext:^(NSError *_Nullable x){
@strongify(self)
self.error = nil;
self.data = nil;
self.status = HTTPRequestStatusError;
}];
}
/**
懒加载方式来初始化 RACCommand 对象:
*/
- (RACCommand *)requestData {
if (!_requestData) {
_requestData = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString* input) {
//NSDictionary *body = @{@"memberCode": input};
// 进行网络操作,同时将这个操作封装成信号 return
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"网络请求回来的数据"];
[subscriber sendCompleted];//告诉外界发送完了
return nil;
}];
}];
}
return _requestData;
}
接下来是控制器的代码:
ViewController.m
@interface RACCommandVC ()
@property (nonatomic, strong, readwrite) RACCommandViewModel *viewModel;
@property (nonatomic, strong, readwrite) UIButton *btn;
@property (nonatomic, strong, readwrite) UITextView *textView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_viewModel = [[RACCommandViewModel alloc] init];
[self.view addSubview:self.btn];
[self.view addSubview:self.textView];
[self bindViewModel];
}
- (void)bindViewModel {
@weakify(self)
[[RACObserve(_viewModel, status) skip:1] subscribeNext:^(NSNumber* x){
switch ([x intValue]) {
case HTTPRequestStatusBegin:
NSLog(@"开始刷新,展示菊花");
break;
case HTTPRequestStatusEnd:
NSLog(@"结束刷新,隐藏菊花");
break;
case HTTPRequestStatusError:
NSLog(@"数据错误");
break;
default:
break;
}
}];
RAC(self.textView, text) = [[RACObserve(_viewModel, data) skip:1]map:^id _Nullable(NSString *value) {
return value;
}];
[[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self)
[self.viewModel.requestData execute:@"96671e1a812e46dfa4264b9b39f3e225"];
}];
}
- (UIButton *)btn {
if (!_btn) {
_btn = [UIButton buttonWithType:UIButtonTypeCustom];
_btn.backgroundColor = [UIColor redColor];
[_btn setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
[_btn setTitle:@"点击刷新" forState:UIControlStateNormal];
_btn.frame = CGRectMake(0, 150, 200, 60);
}
return _btn;
}
- (UITextView *)textView {
if (!_textView) {
_textView = [[UITextView alloc] initWithFrame: CGRectMake(60,300,200,200)];
_textView.backgroundColor = [UIColor greenColor];
}
return _textView;
}
看一下最终的效果图
屏幕快照 2019-03-25 05.36.03 PM.png 屏幕快照 2019-03-25 05.36.13 PM.png 屏幕快照 2019-03-25 05.36.22 PM.png
网友评论