(七)、iOS RAC - RACCommand

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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容