(七)、iOS RAC - RACCommand

2019-03-25  本文已影响0人  Buddha_like
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
上一篇下一篇

猜你喜欢

热点阅读