iOS-ReactiveObjC(RAC) 学习笔记

RAC简介

  • 在RAC中,万物皆信号。
  • RAC 指的就是 RactiveCocoa ,是 Github 的一个开源框架,能够通过信号提供大量方便的事件处理方案,让我们更简单粗暴地去处理事件,现在分为 ReactiveObjC(OC) 和 ReactiveSwift(swift),官方的说,ReactiveCocoa(其简称为RAC)是由GitHub开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程响应式编程的特性。。
  • 团队协作时,必须注意一个点,对于很熟悉RAC的人来说,使用RAC是非常方便的。但对于不熟悉RAC的人来说,由于RAC的可阅读性是很差的,所以需耗费大量时间阅读和学习。
  • 未避免循环引用,需使用@weakify(self),@strongify(self)。这两个宏至少是一对出现的
  • RAC就是一个第三方库,他可以大大简化你的代码过程。

RAC架构框架图


RAC架构框架图
信号流程

一 、使用

老规矩,pod install

pod 'ReactiveObjC', '~> 3.0.0' //建议纯oc代码使用ReactiveObjC

1、基本控件

  • UITextField
//监听文本输入
[[_textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@",x);
}];
//可根据自己想要监听的事件选择
[[_textField rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"%@",x);
    }];
//添加条件 --  下面表示输入文字长度 > 10 时才会调用subscribeNext
[[_textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) { 
    return value.length > 10;
}] subscribeNext:^(NSString * _Nullable x) {
     NSLog(@"输入框内容:%@", x);
}];
  • UIButton
   //监听按钮点击事件
  [[View.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"-->%@",x);
    }];

//这种不建议使用,暂时不知道didBtnAction方法不写会报警高~不过可以也实现
//    [[View.btn rac_signalForSelector:@selector(didBtnAction:)]subscribeNext:^(id x) {
//       NSLog(@"-->%@",x);
//    } completed:^{
//    }];

rac 类似代理传值例子

 //第一
@property (strong,nonatomic) RACSubject *btnSubject;

//第二,在View点m,按钮点击方法里面 
    // view里面带有btn,点击的时候 

- (void)didBtnAction:(UIButton *)btn{
    SWLog(@"回到控制器后g要干嘛?");
   
    [self.btnSubject sendNext:@{@"text":@"这边可以携带参数"}];
    
}

- (RACSubject *)btnSubject{
    if (!_btnSubject) {
        _btnSubject = [RACSubject subject];
    }
    return _btnSubject;
}

//控制器外部调用
    [View.btnSubject subscribeNext:^(id x) {
        SWLog(@"收到!==%@",x);
       // log==> @{@"text":@"这边可以携带参数"}
    } completed:^{
        
    }];
  • 计时器(interval、delay)

    /*
     声明:RACDisposable *disposable
     创建一个RACDisposable,调用disposable方法等时候就会进入创建对象的block,把定时器释放掉
     这两个宏就是为了解决循环引用的,且必须配套使用。
     @weakify(self)
     @strongify(self)
     
     相当于下面:
     __weak typeof(self) weakSelf = self;
     __strong typeof(weakSelf) strongSelf = weakSelf;
     */
    

//类似timer
@weakify(self)
self.disposable = [[RACSignal interval:2 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
        @strongify(self)
        NSLog(@"时间:%@", x); // x 是当前的时间
        //关闭计时器
        [self.disposable dispose];
}];
//延时
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"延时2秒"];
        return nil;
    }] delay:2] subscribeNext:^(id x) {
        
        NSLog(@"-->%@",x);
    }];

2.KVO 属性监听

//方法一
[_redView rac_observeKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
       NSLog(@"方法一 %@",value);
}];
//方法二
[[_redView rac_valuesForKeyPath:@"frame" observer:nil] subscribeNext:^(id  _Nullable x) {
        NSLog(@"方法二 %@",x);
}];
//方法三 宏写法
[RACObserve(_redView, frame) subscribeNext:^(id  _Nullable x) {
        NSLog(@"方法三%@",x);
    }];

3.textField监听,lable赋值,手势

//此处RAC宏相当于让_label订阅了_textField的文本变化信号
//赋值给label的text属性
RAC(_label, text) = _textField.rac_textSignal;

 //快速添加手势
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init];
    [[tap rac_gestureSignal] subscribeNext:^(id x) {
    } completed:^{
    }];
    [newTextLable addGestureRecognizer:tap];

//输入框监听
[[self.textFild rac_textSignal] subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];

4.监听 Notification 通知事件

    //通知常用
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"通知名字" object:nil]subscribeNext:^(id x) {
        
    } completed:^{
        
    }];

5.多个订阅 RACMulticastConnection

- (void)sss{
    
    RACSignal *signal=[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"123444"];
        return nil;
    }];
    RACMulticastConnection *mut = [signal publish];
    [mut.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
    [mut.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"22%@",x);
    }];
    [mut connect];
}

6.绑定信号 bind

- (void)text{
  RACSubject *signal = [RACSubject subject];
  [[signal bind:^RACStreamBindBlock _Nonnull{
     return ^RACSignal *(id value, BOOL* stop){
         NSLog(@"%@",value);
          NSString *v = [NSString stringWithFormat:@"处理过的数据%@",value];
          return [RACReturnSignal return:v];//需要导入头文件#import "RACReturnSignal.h"
        };
  }] subscribeNext:^(id  _Nullable x) {
     NSLog(@"%@",x);
    }];
    [signal sendNext:@"1111"];
    //输出
    //1111
    //处理过的数据1111

}

7.映射

  - (void)text{  
    //映射
    RACSubject *subject = [RACSubject subject];
    [[subject map:^id _Nullable(id  _Nullable value) {
        return [NSString stringWithFormat:@"处理过的数据%@",value];
        
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    } ];
    [subject sendNext:@"11"];
    
    //输出
    //处理过的数据11
}

8.忽略 ignore

- (void)text2{
    RACSubject * subject = [RACSubject subject];
    [[subject ignore:@"a"] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"a"];
    [subject sendNext:@"b"];
    [subject sendNext:@"c"];
    
    
    //输出log
//    2019-03-07 14:35:00.348048+0800 MVVM+RAC[10199:763418] b
//    2019-03-07 14:35:00.348200+0800 MVVM+RAC[10199:763418] c
}

9 RACTuple、RACSequence

1.RACTuple元组类,类似NSArray,用来包装值.
2.RACSequence:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。

  // 1.遍历数组
    NSArray *numbers = @[@1,@2,@3,@4];
 
    // 这里其实是三步
    // 第一步: 把数组转换成集合RACSequence numbers.rac_sequence
    // 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
    // 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
    [numbers.rac_sequence.signal subscribeNext:^(id x) {
 
        NSLog(@"%@",x);
    }];
 
 
    // 2.遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
    NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
 
        // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
        RACTupleUnpack(NSString *key,NSString *value) = x;
 
        // 相当于以下写法
//        NSString *key = x[0];
//        NSString *value = x[1];
 
        NSLog(@"%@ %@",key,value);
 
    }];
 
 
    // 3.字典转模型
    // 3.1 OC写法
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
 
    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
 
    NSMutableArray *items = [NSMutableArray array];
 
    for (NSDictionary *dict in dictArr) {
        FlagItem *item = [FlagItem flagWithDict:dict];
        [items addObject:item];
    }
 
    // 3.2 RAC写法
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
 
    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
 
    NSMutableArray *flags = [NSMutableArray array];
 
    _flags = flags;
 
    // rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
    [dictArr.rac_sequence.signal subscribeNext:^(id x) {
        // 运用RAC遍历字典,x:字典
 
        FlagItem *item = [FlagItem flagWithDict:x];
 
        [flags addObject:item];
 
    }];
 
    NSLog(@"%@",  NSStringFromCGRect([UIScreen mainScreen].bounds));
 
 
    // 3.3 RAC高级写法:
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
 
    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
    // map:映射的意思,目的:把原始值value映射成一个新值
    // array: 把集合转换成数组
    // 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
    NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {
 
        return [FlagItem flagWithDict:value];
 
    }] array];

10 项目开发- RACCommand

RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。

模拟实际开发中,一个获取书架的接口传参数和请求:
1.在viewModel.h 里面声明一个属性
/// 获取书架
@property (nonatomic , strong) RACCommand *get_list_bookcaseCommand;
2.在viewModel.m 进行懒加载实现
#pragma mark - get
- (RACCommand *)get_list_bookcaseCommand
{
    if (_get_list_bookcaseCommand== nil) {
        kWeakObject(self)
        _get_list_bookcaseCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
            return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                kStrongObject(self)
               [CPNetManager POST:get_list_bookcaseUrl parameters:input success:^(NSDictionary * _Nullable responseObject, NSURLSessionDataTask * _Nonnull task) { 
                    [subscriber sendNext:responseObject];
                    [subscriber sendCompleted];
                } failure:^(NSDictionary * _Nullable responseObject, NSString * _Nullable message, NSString * _Nullable code, NSURLSessionDataTask * _Nonnull task) {
                    [subscriber sendNext:nil];
                    [subscriber sendCompleted];
                }]; 
                return nll;
            }];
            
        }];
    }
    return _get_list_bookcaseCommand;
}
3.在controller 进行懒加载viewModel对象再需要请求的作用域去调用传参数和信用监听。
//self.viewModel.get_list_bookcaseCommand  为RACCommand对象 发送参数进行请求,input为参数
 [self.viewModel.get_list_bookcaseCommand execute:@{@"page":@"1",@"uid”: userModel.uid}];
 //获取书架 [self.viewModel.get_list_bookcaseCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        kStrongObject(self)
        [self.viewModel.headRefresh endRefreshing];
        [self.viewModel.footRefresh endRefreshing];
        if (x)
        {
//收到数据x 进行解析
            self.dataModel.model = [HBHomeModel yy_modelWithJSON:x[@"data"]];
            [self.viewModel.collectionView reloadData];
            [self.viewModel.footRefresh endRefreshingWithNoMoreData];
        }
    }]; 
 

11、ReactiveCocoa开发中常见用法。

11.1 代替代理:
rac_signalForSelector:用于替代代理。

11.2 代替KVO :
rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。

11.3 监听事件:
rac_signalForControlEvents:用于监听某个事件。

11.4 代替通知:
rac_addObserverForName:用于监听某个通知。

11.5 监听文本框文字改变:
rac_textSignal:只要文本框发出改变就会发出这个信号。

11.6 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。

12 、项目开发-短信倒计时、登录按钮是否可点击

@property (nonatomic, strong) RACDisposable * disposable;

@property (nonatomic, assign) NSInteger time;

 @weakify(self)
    [[_sendButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
       @strongify(self)
        self.time = 10;
        self.disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
            @strongify(self)
            self.time -- ;
            NSString *text = (self.time > 0) ? [NSString stringWithFormat:@"请稍等%zd秒",self.time] : @"重新发送";
            [self.sendButton setTitle:text forState:UIControlStateNormal];
            self.sendButton.enabled = (self.time == 0);
            if (self.time == 0)   [self.disposable dispose];  //关掉信号
        }];
         
    }];
  //根据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];
    }];

总结 以上就是RAC的用法,希望对大家有帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容