iOS进阶+实战MVVM模式RAC博客收录

iOS-ReactiveCocoa使用之RACCommand

2016-12-10  本文已影响7203人  Tangentw

前言

前几天开始研究Cocoa的第三方编程框架ReactiveCocoa,其使用响应式、函数式的编程思想,对于初识者来说较为抽象,从RACSignalRACCommand,我花了不少时间去搞懂它们如何使用。其中,花费我最多时间去掌握的就是RACCommand,这货虽然刚开始难以理解难以使用,但是,当我初步了解其特性与应用后,我才发现了它是如此的强大。
下面就我对RACCommand的理解,来阐述它的基本介绍以及相关使用方法。

初识 RACCommand

创建 RACCommand

RACCommand的创建有两种形式:

- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;  ①
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;  ②

第一种就是直接通过传进一个用于构建RACSignalblock参数来初始化RACCommand,而block中的参数input为执行command时传入的数据,另外,创建出的signal可在里面完成一些数据操作,如网络请求,本地数据库读写等等,而第二种则另外还需要传进一个能传递BOOL事件的RACSignal,这个signal的作用相当于过滤,当传递的布尔事件为真值时,command能够执行,反之则不行。

注意: 伴随着command一起构建的signal,记得要在操作完成后发送完成消息以表示其执行完了:

[subscriber sendCompleted];

否则不能再执行此command。

UIButton中有属性rac_command用于绑定一个已经创建好的command(其使用在后面讲到),当你使用第二种方式创建command时,button的enable属性会随command的可执行性而改变,意思是当传递布尔事件的信号传递了真值事件,按钮才可使用。另外,当你按下按钮,command开始执行时,按钮的enable被自动设置成了NO,除非command执行完了,怎么判断command执行完成了呢?就是当其伴随的signal发送完成事件的时候(上面提及到)。

注意: 当button的rac_command已经绑定了某个command,而这个command又是以第二种方式初始化,那么你就不能动态改变button的enable,如:

RAC(self.button, enable) = someSignal;

这样子运行起来会报错。(自己曾踩过的坑)

执行 RACCommand

RACCommand的执行使用下面的这个函数:

- (RACSignal *)execute:(id)input;

在上面已经提及到,input会作为创建command时其内部signal的构建block中的参数,用于传递数据。

订阅 RACCommand

订阅RACCommand我们可以使用其内部的属性executionSignals返回一个信号,然后对这个信号进行订阅。

[[aCommand executionSignals]
    subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

在订阅的block中,我们打印了传递事件x的描述,最后会发现x原来是一个RACSignal,原因是RACCommand中的executionSignals属性是一个包裹着信号的信号,其包裹着的信号就是我们当初在创建RACCommand时进行构建的信号,所以当我们开始执行RACCommand时,executionSignals信号就会立即发送事件,传递出其包裹的信号,我们可以对这个信号进行订阅:

[[aCommand executionSignals]
    subscribeNext:^(RACSignal *x) {
        [x subscribeNext:^(id x) {
            //  Do something...
        }];
    }];

如果你嫌订阅两个事件麻烦的话,可以使用函数switchToLatest进行转换:

[[[aCommand executionSignals]switchToLatest]
    subscribeNext:^(id x) {
        //  Do something...
    }];

这样就比上面少写了一步信号订阅。

如果你想在RACCommand执行时做某些提示操作(弹出等待框,出现转来转去的菊花),并在执行后取消提示,你可以这样写:

[[aCommand executionSignals]
    subscribeNext:^(RACSignal *x) {
        //  开始提示
        [x subscribeNext:^(id x) {
            //  关闭提示
            //  Do something...
        }];
    }];

在对command进行错误处理的时候,我们不应该使用subscribeError:对command的executionSignals进行错误的订阅,因为executionSignals这个信号是不会发送error事件的,那当command包裹的信号发送error事件时,我们要怎样去订阅它呢?这里用到command的一个属性:errors,我们可以这样来对错误进行订阅:

[aCommand.errors
    subscribeNext:^(NSError *x) {
        NSLog(@"ERROR! --> %@",x);
}];

与 RACSubject的区别

虽然ReactiveCocoa的官方说过RACSubject较为灵活,所以建议少用,而我平时会经常使用RACSubject用其代替delegate。在刚开始接触RAC的时候,我会觉得RACCommandRACSubject非常相似,都能够控制执行,都能够进行订阅,然而,它们的区别也是挺大的。

举个栗子吧,用计算机网络中的术语,RACSubject更像“单工”,而RACCommand就类似于“半双工”。

下面的这张图表明了我对RACSubjectRACCommand的理解:

RACCommand 实战

讲解RAC最好的Demo就是Login(登录)界面的构建了,下面我们就来完成一个登录界面,主要使用RACCommand以及MVVM设计模式。

给出的需求:

码代码:

  1. 用Storyboard把界面搭好,调整好布局,连好线,然后把菊花视图隐藏,并创建ViewModel:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *userNameTF;
@property (weak, nonatomic) IBOutlet UITextField *passwordTF;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *juhuaView;
@property (strong, nonatomic) TanLoginViewModel *viewModel;
@end

 @implementation ViewController
- (void)viewDidLoad {
        [super viewDidLoad];
        self.juhuaView.hidden = YES;
        _viewModel = [[TanLoginViewModel alloc]init];
    
    }
@end
  1. 模拟网络请求,创建Networker,其包含网络请求的方法,在这方法返回带有登录完成事件的信号:
    @interface TanNetworker : NSObject
    + (RACSignal *)loginWithUserName:(NSString *) name password:(NSString *)password;
    @end
    
    @implementation TanNetworker
+ (RACSignal *)loginWithUserName:(NSString *) name password:(NSString *)password
{
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:[NSString stringWithFormat:@"User %@, password %@, login!",name, password]];
                [subscriber sendCompleted];
        });
            return nil;
    }];
}
@end
  1. 定义登录视图的ViewModel,在里面创建登录的command:
    @interface TanLoginViewModel : NSObject
    @property(nonatomic, copy) NSString *userName;
    @property(nonatomic, copy) NSString *password;
    @property(nonatomic, strong, readonly) RACCommand   *loginCommand;
    @end
    
    @implementation TanLoginViewModel
- (instancetype)init
{
        if (self = [super init]) {
            RACSignal *userNameLengthSig = [RACObserve(self, userName)
                                            map:^id(NSString *value) {
                                                if (value.length > 6) return @(YES);
                                                return @(NO);
                                            }];
            RACSignal *passwordLengthSig = [RACObserve(self, password)
                                            map:^id(NSString *value) {
                                                if (value.length > 6) return @(YES);
                                                return @(NO);
                                            }];
            RACSignal *loginBtnEnable = [RACSignal combineLatest:@[userNameLengthSig, passwordLengthSig] reduce:^id(NSNumber *userName, NSNumber *password){
                return @([userName boolValue] && [password boolValue]);
            }];
        
        
            _loginCommand = [[RACCommand alloc]initWithEnabled:loginBtnEnable signalBlock:^RACSignal *(id input) {
                return [TanNetworker loginWithUserName:self.userName password:self.password];
            }];
        }
        return self;
}
@end
  1. 在控制器中实现RAC,并且订阅command,响应事件:
    @weakify(self)
    RAC(self.viewModel, userName) = self.userNameTF.rac_textSignal;
    RAC(self.viewModel, password) = self.passwordTF.rac_textSignal;
    self.loginBtn.rac_command = self.viewModel.loginCommand;
    [[self.viewModel.loginCommand executionSignals]
    subscribeNext:^(RACSignal *x) {
        @strongify(self)
        self.juhuaView.hidden = NO;
        [x subscribeNext:^(NSString *x) {
            self.juhuaView.hidden = YES;
            NSLog(@"%@",x);
        }];
    }];

到这里,一个利用RACCommandMVVM设计模式进行登录操作的小Demo就完成了~

跑起来

下面就让我们来测试一下这个小Demo

上一篇 下一篇

猜你喜欢

热点阅读