美文网首页
ReactiveCocoa操作和实战应用

ReactiveCocoa操作和实战应用

作者: Hsusue | 来源:发表于2018-10-03 20:22 被阅读18次

前言

在上一篇ReactiveCocoa基础类学习笔记中,笔者从初学者的角度把RAC基础类梳理了一遍,但学完还是不知道怎么应用。这篇将带大家进一步探索RAC各种理论和实战应用。


RAC 操作

RAC实在有太多的方法了,同样的问题能采取多种方法实现。笔者在本文只选取了基本且能 实现其他方法效果 的方法。对RAC更多方法有兴趣的可以查看最快让你上手ReactiveCocoa之进阶篇

某种场景可用多种方式实现。开发中喜欢哪种就用哪种吧,没必要全部记住,太多了。汇总成一个表。

image

RAC开发中核心方法——bind(绑定)

在开发中,很少直接用到bind,因为RAC已经封装了很多方法。

虽然很少用,但理解了执行顺序后,会对理解后面有帮助。

    // 1.创建源信号
    RACSubject *subject = [RACSubject subject];
    
    // 2.绑定信号
    RACSignal *bindSignal = [subject bind:^RACStreamBindBlock{
        // 3.1
        // block调用时刻:只要绑定信号被订阅就会调用
        
        
        return ^RACSignal *(id value, BOOL *stop){
            // 4.1
            // block调用:只要源信号发送数据,就会调用
            // block作用:处理源信号内容
            // value:源信号发送的内容
            
            // 处理源信号内容,加点东西
            value = [NSString stringWithFormat:@"加点东西%@",value];
            // 返回信号,不能传nil,返回空信号[mRACSignal empty]
            return [RACReturnSignal return:value];
        };
    }];
    
    // 3.订阅绑定信号
    [bindSignal subscribeNext:^(id x) {
        // 5.
       // blcok:当return [RACReturnSignal return:value],就会调用这个Block
        NSLog(@"接收到绑定信号处理完的信号%@",x);
    }];
    
    // 4.源信号发送数据
    [subject sendNext:@"123"];

信号的映射和过滤

映射:把源信号内容映射成新的内容

两种方式:mapflattenMap
区别:map用于基本的信号,flattenMap常用于信号中信号。

// map用法

    // 创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 绑定信号
    RACSignal *bindSignal = [subject map:^id(id value) {
        // 返回的类型,就是你需要映射的值
        return [NSString stringWithFormat:@"修改内容:%@",value];
        
    }];
    
    // 订阅绑定信号
    [bindSignal subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"233"];

// fluttenMap用法

     RACSubject *signalOfsignals = [RACSubject subject];
    
    RACSubject *signal = [RACSubject subject];
    
    // 订阅信号
    // 注释部分为分部,方便新手理解
//    [signalOfsignals subscribeNext:^(RACSignal *x) {
//        
//        [x subscribeNext:^(id x) {
//            NSLog(@"%@",x);
//        }];
//        
//    }];
//    RACSignal *bindSignal = [signalOfsignals flattenMap:^RACStream *(id value) {
//        // value:源信号发送内容
//        return value;
//    }];
//    
//    [bindSignal subscribeNext:^(id x) {
//        
//        NSLog(@"%@",x);
//    }];
    // 此处连起来处理,对应着注释部分
    [[signalOfsignals flattenMap:^RACStream *(id value) {
        return value;
        
    }] subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
        
    }];
    
    // 发送信号
    [signalOfsignals sendNext:signal];
    [signal sendNext:@"233"];

过滤:获取满足条件的信号。

试想这种场景,文本框长度大于5时,才让按钮可点击。这时就要把文本框的信号过滤出来,长度超过5的信号,再订阅。

    [[self.textField.rac_textSignal filter:^BOOL(id value) {
        // value:源信号的内容
        return  [value length] > 5;
        
    }] subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];

组合信号

组合(concat):信号按顺序连接。

比如先执行完signalAdidSubscribe,再执行signalBdidSubscribe

虽然可以在signalAdidSubscribe中按顺序拼上signalBdidiSubscribe。但用这种方法能聚合起来。

    // 组合
    
    // 创建信号A
    RACSignal *siganlA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // 网络请求 根据url下载图片1
        // 成功后 发送信号
        [subscriber sendNext:@"信号A"];
        
        [subscriber sendCompleted];
        
        return nil;
    }];
    
    RACSignal *siganlB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 网络请求 根据url下载图片2
        // 成功后 发送信号
        [subscriber sendNext:@"信号B"];
        
        return nil;
    }];
    
    // concat:按顺序去连接
    // 注意:concat,第一个信号必须要调用sendCompleted
    // 创建组合信号
    RACSignal *concatSignal = [siganlA concat:siganlB];
    
    // 订阅组合信号
    [concatSignal subscribeNext:^(id x) {
        
        // 既能拿到A信号的值,又能拿到B信号的值
        NSLog(@"%@",x);
        
    }];

image

concatSignal被订阅,就会按顺序订阅被组合信号。

注意的是,signalA[subscriber sendCompleted]

signalA[subscriber sendNext]将会执行concatSignalnext


压缩信号

zipWith(压缩):两个信号都执行过后再执行某个任务。和上一篇文章提到的综合信号结果rac_liftSelector 一样的效果。

// 创建信号A
    RACSubject *signalA = [RACSubject subject];
    
    // 创建信号B
    RACSubject *signalB = [RACSubject subject];
    
    // 压缩成一个信号
    // zipWith:当一个界面多个请求的时候,要等所有请求完成才能更新UI
    // zipWith:等所有信号都发送内容的时候才会调用
    RACSignal *zipSignal = [signalA zipWith:signalB];
    
    // 订阅信号
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 发送信号
    [signalB sendNext:@2];
    [signalA sendNext:@1];

合并信号并聚合元组

开发中常用。其和压缩信号的区别是:压缩信号要两个信号都执行过,组合信号不用。

组合两个文本框的信号,最终让登录按钮是否可交互。

// 组合
    // 组合哪些信号
    // reduce:聚合
    
    // reduceBlock参数:根组合的信号有关,一一对应
    RACSignal *comineSiganl = [RACSignal combineLatest:@[_accountFiled.rac_textSignal,_pwdField.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
        // block:只要源信号发送内容就会调用,组合成新一个值
        NSLog(@"%@ %@",account,pwd);
        // 聚合的值就是组合信号的内容
       
        return @(account.length && pwd.length);
    }];
    
    // 订阅组合信号
//    [comineSiganl subscribeNext:^(id x) {
//        _loginBtn.enabled = [x boolValue];
//    }];
    
    RAC(_loginBtn,enabled) = comineSiganl;

RAC + MVVM 实战

如果你对MVVM不了解,可以看这篇文章iOS 关于MVVM With ReactiveCocoa设计模式的那些事

这里简单说一下MVVM。

  • M(模型):保存视图数据。
  • V(视图+控制器):展示内容+如何展示。
  • VM(视图模型):处理展示的业务逻辑,包括按钮点击,数据的请求、解析等。
  • 每一个控制器都对应一个VM。

下面将一步步走进MVVM的世界。


笔者学的时候例子是登录界面,写出来加深一下理解。

要做到 账号和密码文本框内容符合条件后,登录按钮才能点击。

还不理解MVVM的你可能在viewDidLoad写出以下代码。

    // 1.处理文本框业务逻辑
    RACSignal *loginEnableSiganl = [RACSignal combineLatest:@[_accountFiled.rac_textSignal,_pwdField.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
        return @(account.length && pwd.length >= 8);
    }];
    
    // 设置按钮能否点击
    RAC(_loginBtn,enabled) = loginEnableSiganl;
    
    // 创建登录命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                // 发送登录请求
                // 成功后发送数据
                [subscriber sendNext:@"请求登录的数据"];
                [subscriber sendCompleted];
            });
            return nil;
        }];
    }];
    
    // 订阅命令中信号源
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 监听命令执行过程
    [[command.executing skip:1] subscribeNext:^(id x) {
        if ([x boolValue] == YES) {
            // 正在执行
            NSLog(@"正在执行");
            // 显示蒙版
            [MBProgressHUD showMessage:@"正在登录..."];
            
        }else{
            // 执行完成
            // 隐藏蒙版
            [MBProgressHUD hideHUD];
            
            NSLog(@"执行完成");
        }
        
    }];
    
    // 监听登录按钮点击
    [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"点击登录按钮");
        // 处理登录事件
        [command execute:nil];
        
    }];

能理解以上代码说明你掌握了RAC的基础用法。但是要应用到MVVM架构就要转到重点VM中了。

这时候每个控制器就要创建一个LoginViewModel,在VM.h中暴露出信号接口用于绑定。

@interface LoginViewModel : NSObject
@property (nonatomic, strong, readonly) RACSignal *loginEnableSiganl;
@property (nonatomic, strong, readonly) RACCommand *loginCommand;
@end

但发现loginCommand可以直接放进去;loginEnableSiganl要拿到文本框的值,然而VM中是不能引用UI的。

于是在VM.h中加上了账号和密码。

@interface LoginViewModel : NSObject

@property (nonatomic, strong) NSString *account;
@property (nonatomic, strong) NSString *pwd;

@property (nonatomic, strong, readonly) RACSignal *loginEnableSiganl;
@property (nonatomic, strong, readonly) RACCommand *loginCommand;

@end

然后在控制器中给该属性绑定信号。做到实时对应文本框内容。并且把按钮可否点击绑定到信号中。

-(void)viewDidLoad {
    RAC(self.loginVM, account) = _accountField.rac_textSignal;
    RAC(self.loginVM, pwd) = _pwdField.rac_textSignal;
    RAC(_loginBtn, enabled) = self.loginVM.loginEnableSignal;
    [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        // 处理登录事件
        [self.loginVM.loginCommand execute:nil];
        
    }];
}

这时候在VM中就能处理登录信号了。当然loginCommand可以直接放进去。所以VM.m这时将会变成这样。

//LoginViewModel.m
@implementation LoginViewModel
- (instancetype)init
{
    if (self = [super init]) {
        [self setUp];
    }
    return self;
}

- (void)setUp
{
    _loginEnableSiganl = [RACSignal combineLatest:@[RACObserve(self, account),RACObserve(self, pwd)] reduce:^id(NSString *account,NSString *pwd){
        return @(account.length && pwd.length >= 8);
    }];
    
    _loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                // 发送登录请求
                // 成功后发送数据
                [subscriber sendNext:@"请求登录的数据"];
                [subscriber sendCompleted];
            });
            return nil;
        }];
    }];
    
    [_loginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [[_loginCommand.executing skip:1] subscribeNext:^(id x) {
        if ([x boolValue] == YES) {
            // 正在执行
            NSLog(@"正在执行");
            // 显示蒙版
            [MBProgressHUD showMessage:@"正在登录.."];
        }else{
            // 执行完成
            // 隐藏蒙版
            [MBProgressHUD hideHUD];
            NSLog(@"执行完成");
        }
    }];
}
@end

在本文开头引用的进阶篇内还有结合网络的实战,本文就不再阐述了。

最后来总结一下怎么用RAC + MVVM。

  1. 为VM准备好view需要的数据和命令。写好初始化方法。
  2. 在控制器中,写好view。
  3. 在控制器中,将信号绑定好。

最后,相信学完基础类和基础应用,这篇文章能帮你更好地理解。iOS 关于MVVM With ReactiveCocoa设计模式的那些事

相关文章

网友评论

      本文标题:ReactiveCocoa操作和实战应用

      本文链接:https://www.haomeiwen.com/subject/joktaftx.html