RACCommand 是 RAC 中的最復(fù)雜的一個(gè)類之一,它也是一種廣義上的信號(hào),RAC 中信號(hào)其實(shí)是一種對(duì)象(或者是不同代碼塊)之間通信機(jī)制,在面向?qū)ο笾?類之間的通信方式主要是方法調(diào)用,而信號(hào)也是一種調(diào)用,只不過它是函數(shù)式的,因此信號(hào)不僅僅可以在對(duì)象之間相互調(diào)用(傳參),也可以在不同代碼塊(block)之間進(jìn)行調(diào)用。
一般來說,RAC 中用 RACSignal 來代表信號(hào)。一個(gè)對(duì)象創(chuàng)建 RACSignal 信號(hào),創(chuàng)建信號(hào)時(shí)會(huì)包含一個(gè) block,這個(gè) block 的作用是發(fā)送信號(hào)給訂閱者(類似方法返回值或回調(diào)函數(shù))。另一個(gè)對(duì)象(或同一個(gè)對(duì)象)可以用這個(gè)信號(hào)進(jìn)行訂閱,從而獲得發(fā)送者發(fā)送的數(shù)據(jù)。這個(gè)過程和方法調(diào)用一樣,信號(hào)相當(dāng)于暴露給其它對(duì)象的方法,訂閱者訂閱信號(hào)相當(dāng)于調(diào)用信號(hào)中的方法(block),只不過返回值的獲得變成了通過 block 來獲得。此外,你無法直接向 RACSignal 傳遞參數(shù),要向信號(hào)傳遞參數(shù),需要提供一個(gè)方法,將要傳遞的參數(shù)作為方法參數(shù),創(chuàng)建一個(gè)信號(hào),通過 block 的捕獲局部變量方式將參數(shù)捕獲到信號(hào)的 block 中。
而 RACCommand 不同,RACCommand 的訂閱不使用 subscribeNext 方法而是用 execute: 方法。而且 RACCommand 可以在訂閱/執(zhí)行(即 excute:方法)時(shí)傳遞參數(shù)。因此當(dāng)需要向信號(hào)傳遞參數(shù)的時(shí)候,RACComand 更好用。
此外,RACCommand 包含了一個(gè) executionSignal 的信號(hào),這個(gè)信號(hào)是對(duì)用戶透明的,它是自動(dòng)創(chuàng)建的,由 RACCommand 進(jìn)行管理。許多資料中把它稱之為信號(hào)中的信號(hào),是因?yàn)檫@個(gè)信號(hào)會(huì)發(fā)送其它信號(hào)——即 RACCommand 在初始化的 signalBlock 中創(chuàng)建(return)的信號(hào)。這個(gè)信號(hào)是 RACCommand 創(chuàng)建時(shí)由我們創(chuàng)建的,一般是用于處理一些異步操作,比如網(wǎng)絡(luò)請(qǐng)求等。
先來看看RACCommand 基礎(chǔ)寫法
/**
Command翻譯過來就是命令
*/
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
NSLog(@"執(zhí)行1");
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"執(zhí)行3");
[subscriber sendNext:input];
return nil;
}];
}];
/**
executionSignals就是用來發(fā)送信號(hào)的信號(hào)源,
需要注意的是這個(gè)方法一定要在執(zhí)行execute方法之前,否則就不起作用了,
*/
[command.executionSignals subscribeNext:^(id _Nullable x) {
[x subscribeNext:^(id _Nullable x) {
NSLog(@"執(zhí)行4");
NSLog(@"接收數(shù)據(jù)%@",x);
}];
NSLog(@"執(zhí)行2");
}];
[command execute:@"發(fā)送消息"];
整體看下來,發(fā)現(xiàn),它與單純的RACSignal是有一些區(qū)別的,從代碼上我們直觀的可以發(fā)現(xiàn),RACCommand 執(zhí)行順序
1).RACCommand通過execute發(fā)送一個(gè)參數(shù);
2).這時(shí)候會(huì)執(zhí)行 initWithSignalBlock塊,這個(gè)內(nèi)部將創(chuàng)建并返回一個(gè)信號(hào),RACCommand 通過executionSignals(也是一個(gè)信號(hào)) 找到返回信號(hào)并發(fā)送信號(hào)
3).信號(hào)內(nèi)部進(jìn)行發(fā)送消息.
4).訂閱信號(hào),接受信息.
通過這個(gè)特性,我們可以做一個(gè)簡(jiǎn)單的Demo。
項(xiàng)目最終實(shí)現(xiàn)目的,視圖上 一個(gè)按鈕 一個(gè)TextView, 當(dāng)我們點(diǎn)擊這個(gè)按鈕的時(shí)候,會(huì)刷新TextView上的文字.
項(xiàng)目文件構(gòu)成分成兩個(gè)文件 一個(gè)ViewController控制器,另一個(gè)是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 中封裝了各種信號(hào),我們只用到了外層信號(hào)(executionSignal)和內(nèi)層信號(hào)。訂閱這些信號(hào)能夠讓我們實(shí)現(xiàn)兩個(gè)目的:拿到請(qǐng)求返回的數(shù)據(jù)、跟蹤 RACCommand 開始結(jié)束狀態(tài)。定義一個(gè)方法來做這些事情:
*/
/**
訂閱外層信號(hào)(即 executionSignals)。外層信號(hào)在訂閱或執(zhí)行(即 execute: )時(shí)發(fā)送。因此我們可以將它視作請(qǐng)求即將開始之前的信號(hào),在這里將 self.error 清空,將 requestStatus 修改為 begin。
訂閱內(nèi)層信號(hào),因?yàn)閮?nèi)層信號(hào)由外層信號(hào)(executionSignals)作為數(shù)據(jù)發(fā)送(sendNext:),而發(fā)送的數(shù)據(jù)一般是作為 subcribeNext:時(shí)的 block 的參數(shù)來接收的,因此在這個(gè)塊中,塊的參數(shù)就是內(nèi)層信號(hào)。這樣我們就可以訂閱內(nèi)層信號(hào)了,同時(shí)獲取數(shù)據(jù)(保存到 data 屬性)并修改 requestStatus 為 end。
RACCommand 比較特殊的一點(diǎn)是 error 信號(hào)需要在 errors 中訂閱,而不能在 executionSignals 中訂閱。在這里我們訂閱了 errors 信號(hào),并修改 data、error 和 requestStatus 屬性值。
*/
- (void)subscriberConmandSignals {
@weakify(self)
//1. 訂閱外層信號(hào)
[self.requestData.executionSignals subscribeNext:^(RACSignal* innerSignal) {
@strongify(self)
// 2. 訂閱內(nèi)層信號(hào)
[innerSignal subscribeNext:^(NSDictionary *x){
self.data = x;
self.status = HTTPRequestStatusEnd;
NSLog(@"11111111");
}];
self.error = nil;
self.status = HTTPRequestStatusBegin ;
}];
// 3. 訂閱 errors 信號(hào)
[self.requestData.errors subscribeNext:^(NSError *_Nullable x){
@strongify(self)
self.error = nil;
self.data = nil;
self.status = HTTPRequestStatusError;
}];
}
/**
懶加載方式來初始化 RACCommand 對(duì)象:
*/
- (RACCommand *)requestData {
if (!_requestData) {
_requestData = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString* input) {
//NSDictionary *body = @{@"memberCode": input};
// 進(jìn)行網(wǎng)絡(luò)操作,同時(shí)將這個(gè)操作封裝成信號(hào) return
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"網(wǎng)絡(luò)請(qǐng)求回來的數(shù)據(jù)"];
[subscriber sendCompleted];//告訴外界發(fā)送完了
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(@"結(jié)束刷新,隱藏菊花");
break;
case HTTPRequestStatusError:
NSLog(@"數(shù)據(jù)錯(cuò)誤");
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:@"點(diǎn)擊刷新" 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