美文网首页iOSRACiOS开发
响应式编程之ReactiveObjC常见用法

响应式编程之ReactiveObjC常见用法

作者: 奇怪的她的他 | 来源:发表于2018-01-30 16:48 被阅读1668次

    ReactiveObjC是ReactiveCocoa系列的一个OC方面用得很多的响应式编程三方框架,也就是大家经常提到的RAC,也许大家对RAC还是很了解,不知道这个框架用来干嘛,有什么好处,当你看了以下demo后,你会发现,RAC真的很强大,并且你会不知不觉的爱上它。

    一、首先你要使用这个三方框架,必须先用pod导入这个框架。在podfile中添加下面这一句。

    pod 'ReactiveObjC'

    二、接下来我们来看看具体使用方式,让你彻底感受一下它的魅力。

    (1)代替监听事件方法(按钮点击)

    -(void)demo1{
    //创建一个按钮
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(50, 50, 70, 70)];
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    btn.tag = 1001;
    //监听点击事件
    [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"按钮点击了%@",x);
    }];
    }
    

    (2)代替KVO

    -(void)demo2{
    //创建一个按钮
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 70, 70)];
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    //监听点击事件
    [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        //改变btn的Frame
        x.frame = CGRectMake(100,100,200, 200);
    }];
    [[btn rac_valuesAndChangesForKeyPath:@"frame" options:(NSKeyValueObservingOptionNew) observer:self] subscribeNext:^(RACTwoTuple<id,NSDictionary *> * _Nullable x) {
        //RACTwoTuple是一个集合类型,可以用数组的方式获取到里面的内容。
        NSLog(@"frame改变了%@",x.second);
    }];
    //这样的KVO你可以觉得好像并没有多了不起,那你看看demo3。
    }
    

    (3)代替代理,例如自定义view中的某个控件传值到controller

    新建一个自定义view——RACView,重写初始化方法。

    在RACView.h文件中定义一个rac信号属性

    @property (nonatomic,strong)RACSubject *btnClickSingle;
    

    在.m文件中重写初始化方法,发送信号

    -(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor redColor];
        //创建一个按钮
        UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 70, 70)];
        btn.backgroundColor = [UIColor redColor];
        [self addSubview:btn];
        [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
            //发送信号
            [self.btnClickSingle sendNext:@"按钮点击咯"];
            }];
        }
        return self;
    }
    -(RACSubject *)btnClickSingle{
        if (!_btnClickSingle) {
            _btnClickSingle = [RACSubject subject];
        }
        return _btnClickSingle;
    }
    

    在ViewController中

    //只能代替没有返回值的代理
    -(void)demo3{
        RACView *racView = [[RACView alloc]initWithFrame:CGRectMake(0, 0, 300, 300)];
        [self.view addSubview:racView];
        //替代了代理把值从racView中传了过来。
        [racView.btnClickSingle subscribeNext:^(id  _Nullable x) {
            //要传多个值,,可以传一个集合
            NSLog(@"%@",x);
        }];
    }
    

    (4)简化第三种代理传值

    同样先自定义一个RACView,重写初始化方法

    -(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor redColor];
        //创建一个按钮
        UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 70, 70)];
        btn.backgroundColor = [UIColor redColor];
        [self addSubview:btn];
        [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
            //发送信号
            [self sendValue:@"1234" andDic:@{@"key":@"value"}];//这句是demo4的
    
        }];
    }
    return self;
    }
    

    在.h文件中定义一个方法,不需要实现

    //demo4用
    -(void)sendValue:(NSString *)str andDic:(NSDictionary *)dic;
    

    在ViewController中

    -(void)demo4{
    //监听指定的某个方法传参
    RACView *racView = [[RACView alloc]initWithFrame:CGRectMake(0, 0, 300, 300)];
    [self.view addSubview:racView];
    [[racView rac_signalForSelector:@selector(sendValue:andDic:)] subscribeNext:^(RACTuple * _Nullable x) {
        //当有多个参数传递时,传递过来的是集合,假如要取集合中某个元素的内容,可以用一下方式
        NSLog(@"按钮点击了%@",x.first);
    }];
    }
    

    (5)代替通知

    -(void)demo5{
    //创建一个文本输入框
    UITextField *field = [[UITextField alloc]initWithFrame:CGRectMake(50, 50, 200, 50)];
    field.backgroundColor = [UIColor grayColor];
    [self.view addSubview:field];
    
    // 监听键盘弹出事件
    [[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] takeUntil:[self rac_willDeallocSignal]] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@", x);
    }];
    }
    

    (6)监听文本输入框文字的变化

    -(void)demo6{
        //创建一个文本输入框
        UITextField *field = [[UITextField alloc]initWithFrame:CGRectMake(50, 50, 200, 50)];
        field.backgroundColor = [UIColor grayColor];
        [self.view addSubview:field];
        [field.rac_textSignal subscribeNext:^(id x) {
            NSLog(@"文字改变了%@",x);
        }];
    }
    

    (7)代替timer循环执行某个方法

    -(void)demo7{
        [[RACSignal interval:1.0 onScheduler:[RACScheduler scheduler]] subscribeNext:^(NSDate * _Nullable x) {
            NSLog(@"%@",x);
        }];
    }
    

    (8)RAC代替timer实现登录验证码倒计时

    -(void)demo8{
        UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(40, 70, 200, 50)];
        btn.titleLabel.textAlignment = NSTextAlignmentCenter;
        btn.backgroundColor = [UIColor greenColor];
        [btn setTitle:@"发送验证码" forState:(UIControlStateNormal)];
        [self.view addSubview:btn];
        [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
            self.time = 10;
            btn.enabled = NO;
            [btn setTitle:[NSString stringWithFormat:@"请稍等%d秒",_time] forState:UIControlStateDisabled];
            _disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
                //减去时间
                _time --;
                //设置文本
                NSString *text = (_time > 0) ? [NSString stringWithFormat:@"请稍等%d秒",_time] : @"重新发送";
                if (_time > 0) {
                    btn.enabled = NO;
                    [btn setTitle:text forState:UIControlStateDisabled];
                }else{
                    btn.enabled = YES;
                    [btn setTitle:text forState:UIControlStateNormal];
                    //关掉信号
                    [_disposable dispose];
                }
                
            }];
        }];
        
    }
    

    注意这个方法需要定义两个属性

    @property (nonatomic,assign)int time;
    @property (nonatomic,strong)RACDisposable *disposable;
    

    (9)一个方法同时接受多个信号

    -(void)demo9{
        // 处理多个请求,都返回结果的时候,统一做处理.
        RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 发送请求1
            [subscriber sendNext:@"发送请求1"];
            return nil;
        }];
        
        RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 发送请求2
            [subscriber sendNext:@"发送请求2"];
            return nil;
        }];
        
        // 使用注意:几个信号,selector的方法就几个参数,每个参数对应信号发出的数据。
        // 不需要订阅:不需要主动订阅,内部会主动订阅
        [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
        
    }
    // 更新UI
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
        NSLog(@"更新UI%@ %@",data,data1);
    }
    

    二、ReactiveObjC中常用的宏

    (1)RACObserve(就是一个宏定义):快速的监听某个对象的某个属性改变

    -(void)demo10{
        // 监听self.view的center属性,当center发生改变的时候就会触发NSLog方法
        [RACObserve(self.view, center) subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    }
    

    (2)用来给某个对象的某个属性绑定信号,只要产生信号内容,就会把内容给属性赋值

    -(void)demo11{
        //创建一个文本输入框
        UITextField *field = [[UITextField alloc]initWithFrame:CGRectMake(50, 120, 200, 50)];
        field.backgroundColor = [UIColor grayColor];
        [self.view addSubview:field];
        //创建一个label
        UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(50, 50, 200, 50)];
        [self.view addSubview:label];
        //将输入框内容给label
        RAC(label,text) = field.rac_textSignal;
    }
    

    (3)登录按钮的状态根据账号和密码输入框内容的长度来改变

    -(void)demo12{
        UITextField *userNameTF = [[UITextField alloc]initWithFrame:CGRectMake(40, 70, 200, 50)];
        UITextField *passwordTF = [[UITextField alloc]initWithFrame:CGRectMake(40, 130, 200, 50)];
        userNameTF.placeholder = @"请输入用户名";
        passwordTF.placeholder = @"请输入密码";
        [self.view addSubview:userNameTF];
        [self.view addSubview:passwordTF];
        UIButton *loginBtn = [[UIButton alloc]initWithFrame:CGRectMake(40, 180, 200, 50)];
        [loginBtn setTitle:@"马上登录" forState:UIControlStateNormal];
        [self.view addSubview:loginBtn];
        //根据textfield的内容来改变登录按钮的点击可否
        RAC(loginBtn, enabled) = [RACSignal combineLatest:@[userNameTF.rac_textSignal, passwordTF.rac_textSignal] reduce:^id _Nullable(NSString * username, NSString * password){
            return @(username.length >= 11 && password.length >= 6);
        }];
        //根据textfield的内容来改变登录按钮的背景色
        RAC(loginBtn, backgroundColor) = [RACSignal combineLatest:@[userNameTF.rac_textSignal, passwordTF.rac_textSignal] reduce:^id _Nullable(NSString * username, NSString * password){
            return (username.length >= 11 && password.length >= 6) ? [UIColor redColor] : [UIColor grayColor];
        }];
        
    }
    

    (4)避免循环引用,外部@weakify(self),内部@strongify(self)

    -(void)demo13{
        // @weakify() 宏定义
        @weakify(self) //相当于__weak typeof(self) weakSelf = self;
        RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            @strongify(self)  //相当于__strong typeof(weakSelf) strongSelf = weakSelf;
            NSLog(@"%@",self.view);
            return nil;
        }];
        _signal = signal;
    }
    

    三、总结:

    RAC的操作类似于iOS中系统的通知,整个流程就三个步骤,初始化信号,订阅信号,发送信号。只是框架本身许多方法隐藏了其中的一步或者两步,在框架内部实现了,所以我们用起来会很顺手。RAC为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起——以Block的方式,这样非常方便我们管理,就不需要跳到对应的方法里。

    四、最后为大家提供demo下载地址,可能更方便你学习理解。

    github下载地址

    相关文章

      网友评论

      • CesarXie:demo3的自定义view的.m文件里,好像应该加上self.btnClickSingle = [RACSubject subject]:grin:
        CesarXie:@奇怪的她的他 :joy: 原谅我没看Demo,是我偷懒了
        奇怪的她的他:@CesarXie 你看我的demo里面有个懒加载,就是初始化的btnClickSingle,博客里面漏掉了
      • 再美也只是回忆:pod 安装 'ReactiveObjC'为啥不成功呢,报错
        Installing ReactiveObjC (3.1.0)
        [!] /usr/local/bin/bash -c
        set -e
        find -E . -type f -not -name 'RAC*' -regex '.*(EXT.*|metamacros)\.[hm]$' \
        -execdir mv '{}' RAC'{}' \;
        find . -regex '.*\.[hm]' \
        -exec perl -pi \
        -e 's@"(?:(?!RAC)(EXT.*|metamacros))\.h"@"RAC\1.h"@' '{}' \;
        find . -regex '.*\.[hm]' \
        -exec perl -pi \
        -e 's@<ReactiveObjC/(?:(?!RAC)(EXT.*))\.h>@<ReactiveObjC/RAC\1.h>@' '{}' \;

        find: unknown predicate `-E'
        奇怪的她的他:我又重新pod了一次,能行的,是不是cocoapods的问题哦

      本文标题:响应式编程之ReactiveObjC常见用法

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