ReactiveCocoa

一. ReactiveCocoa简介

ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。

  1. ReactiveCocoa作用
  • 在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。 比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。

  • 其实这些事件,都可以通过RAC处理。 ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。

  1. ReactiveCocoa编程思想

在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的Three20框架,在当时也是神器,但是后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想编程思想的由来:在开发中我们会遇见各种各样的需求,经常会思考如何快速的完成这些需求,这样就会慢慢形成快速完成这些需求的思想。

  • 先简单介绍下目前咱们已知的编程思想。
  1. 面向过程:处理事情以过程为核心,一步一步的实现。
  2. 面向对象:万物皆对象
  3. 链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
  • 链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)代表:masonry框架。
 // 创建红色view
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
 // 设置约束,一定要先把view添加上去,才能设置约束
    [self.view addSubview:redView];
   
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        // 链式编程思想特点:方法返回值必须要方法调用者
        // block:把需要操作的值当做block参数,block也需要返回值,就是方法调用者
        // 给make添加left,top约束(保存在make数组),调用equalTo给这两个约束赋值(该值保存在约束)
        make.left.top.equalTo(@10);
        make.right.bottom.equalTo(@-10);
    }];
    
    /*
        mas_makeConstraints执行流程:
        1.创建约束制造者MASConstraintMaker,绑定控件,生成了一个保存所有约束的数组
        2.执行mas_makeConstraints传入进行的block   
        3.让约束制造者安装约束
            *   1.清空之前的所有约束
            *   2.遍历约束数组,一个一个安装
     */  
  1. 响应式编程思想
    不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。代表:KVO运用。
// 监听方法本质:并不需要修改方法的实现,仅仅想判断下有没有调用
//`KVO的本质`:就是监听一个对象有没有调用set方法
@interface Person : NSObject
{
    @public
    NSString *_name;
}
/**  */
@property (nonatomic, strong) NSString *name;
@end
Person *p = [[Person alloc] init];
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
//通过此种方式修改成员变量KVO无法监听
_p -> _name = [NSString stringWithFormat:@"%d",i];

//KVO的底层实现
// 1.苹果内部自定义NSKVONotifying_Person(Person类的子类)
// 2.修改当前对象p的isa指针,指向子类([NSKVONotifying_Person class]),并保存观察者。
// 3.重写这个类的set方法,在其中找到并通知观察者。(在内部恢复父类做法)
//@implementation NSObject (KVO)
- (void)x_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
   // 把观察者保存到当前对象
    objc_setAssociatedObject(self, (__bridge const void *)(observerKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // 修改对象isa指针
    object_setClass(self, [XMGKVONotifying_Person class]);  
}
//@implementation XMGKVONotifying_Person
- (void)setName:(NSString *)name
{
    [super setName:name];
    id obsetver = objc_getAssociatedObject(self, observerKey);
    [obsetver observeValueForKeyPath:@"name" ofObject:self change:nil context:nil];  
}
  1. 函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。
  • 函数式编程本质: 就是往方法中传入Block,方法中嵌套Block调用,把代码聚合起来管理
  • 函数式编程特点: 每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)代表:ReactiveCocoa。
 CalculateManager *mgr = [[CalculateManager alloc] init];
 int result = [[mgr calculate:^(int result){
       // 存放所有的计算代码
        result += 5;
        result *= 5;
        return result;
 }] result];
NSLog(@"%d",result);
  1. ReactiveCocoa编程思想
    ReactiveCocoa结合了几种编程风格:函数式编程(Functional Programming),响应式编程(Reactive Programming)。所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP)框架。以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。

二. ReactiveCocoa使用

先要搞清楚框架中常用的类,在RAC中最核心的类RACSiganl,搞定这个类就能用ReactiveCocoa开发了。

1. RACSiganl
  • 信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。
  • 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发
    //RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据
    // 1.创建信号(冷信号)
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        //3.订阅者发送数据
        [subscriber sendNext: @1]; 
        return  nil;
    }];
    // 2.订阅信号(热信号) // 只要订阅RACDynamicSignal,就会执行1中block
    [signal subscribeNext:^(id  _Nullable x) {
        // nextBlock调用:只要订阅者发送数据就会调用
        NSLog(@"---%@",x);
    }];
RACSignal底层实现

RACSignal底层实现:
1.创建信号:首先把block保存到创建的信号RACDynamicSignal中,此时为冷信号,还不会触发。
2.信号被订阅:也就是调用signal的subscribeNext:nextBlock
- subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
- subscribeNext内部会调用siganl中1保存的block。
3.发送数据:siganl保存的block中调用[subscriber sendNext:@1]; sendNext底层其实就是执行subscriber的nextBlock

2. RACDisposable

用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。

    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber ) {
       self->_subscriber = subscriber;//3若订阅者存在,则不会自动取消信号订阅
         // 3.发送信号
        [subscriber sendNext:@"123"];// 1默认一个信号发送数据完毕们就会主动取消订阅.
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信号被取消订阅了");// 2只要信号取消订阅就会来这
        }];
    }];
    // 2.订阅信号
    RACDisposable *disposable = [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [disposable dispose];// 4取消订阅信号(订阅者一直存在的情况)
3. RACSubject:继承RACSignal

信号提供者,自己可以充当信号,又能发送信号。通常用来代替代理。

 // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    // 2.订阅信号 不同信号订阅的方式不一样
    // RACSubject处理订阅:仅仅是保存订阅者
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅者一接收到数据:%@",x);
    }];
    [subject subscribeNext:^(id  _Nullable x) {
       NSLog(@"订阅者二接收到数据:%@",x);
    }];
    // 3.发送数据  底层实现:遍历所有的订阅者,调用订阅者下保存的block如上
    [subject sendNext:@1];
未命名.png
4. RACReplaySubject :继承RACSubject

重复提供信号类,RACReplaySubject可以先发送数据,在订阅信号,RACSubject就不可以

//跟RACSiganl不一样,创建信号时没有block
RACReplaySubject *subject = [RACReplaySubject subject];
    // 遍历所有的值,创建一个订阅者去发送数据
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅者1======%@",x);
    }];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅者2======%@",x);
    }];
    
    //1.保存值 2.遍历所有的订阅者,发送数据
    [subject sendNext:@1];
    [subject sendNext:@2];
    
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅者3 ======%@",x);
    }];
//    2019-06-03 16:03:37.114715+0800 RAC[66683:1975972] 订阅者1======1
//    2019-06-03 16:03:37.114889+0800 RAC[66683:1975972] 订阅者2======1
//    2019-06-03 16:03:37.114971+0800 RAC[66683:1975972] 订阅者1======2
//    2019-06-03 16:03:37.115065+0800 RAC[66683:1975972] 订阅者2======2

//    2019-06-03 16:03:37.120068+0800 RAC[66683:1975972] 订阅者3 ======1
//    2019-06-03 16:03:37.120205+0800 RAC[66683:1975972] 订阅者3 ======2
//    2019-06-03 16:03:37.120321+0800 RAC[66683:1975972] 订阅者4 ======1
//    2019-06-03 16:03:37.120403+0800 RAC[66683:1975972] 订阅者4 ======2

   // RACReplaySubject:可以先发送信号,在订阅信号.结果大体一致:
RACReplaySubject流程
5.RACTuple:元组类,类似NSArray,用来包装值.
 RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"213",@"321",@1]];  
 NSString *str = tuple[0];
 NSLog(@"%@",str);
6. 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};
 // rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
    [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.  解析plist文件
   NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
   NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
//    NSMutableArray *arr = [NSMutableArray array];
//    [dictArr.rac_sequence.signal subscribeNext:^(NSDictionary *x) {
//        Flag *flag = [Flag flagWithDict:x];
//        [arr addObject:flag];
//    }];
   
    // 高级用法,会把集合中所有元素都映射成一个新的对象
    // map:映射的意思,目的:把原始值value映射成一个新值  array: 把集合转换成数组
    // 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
   NSArray *arr = [[dictArr.rac_sequence map:^id(NSDictionary *value) {
        // value:集合中元素 id:返回对象就是映射的值
        return [Flag flagWithDict:value];
    }] array];
    
    NSLog(@"%@",arr);
7. RACMulticastConnection

用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建.

// RACMulticastConnection:解决重复请求问题
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"发送数据");//connect连接的时候
        [subscriber sendNext:@1];
        return nil;
    }];
 // 创建连接,确定源信号的订阅者RACSubject
 RACMulticastConnection *connection = [signal publish];
// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"1----%@",x);
    }];
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"2----%@",x);
    }];
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"3----%@",x);
    }];
    
    [connection connect];
//2019-06-04 13:57:53.900856+0800 RAC[74055:2113698] 发送数据
//2019-06-04 13:57:53.901065+0800 RAC[74055:2113698] 1----1
//2019-06-04 13:57:53.901157+0800 RAC[74055:2113698] 2----1
//2019-06-04 13:57:53.901254+0800 RAC[74055:2113698] 3----1

RACMulticastConnection底层原理:
1.创建connect,connect.sourceSignal -> RACSignal(原始信号) connect.signal -> RACSubject
2.订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。
3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
3.1订阅原始信号,就会调用原始信号中的didSubscribe
3.2 didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext
4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock

RACMulticastConnection底层流程
8. RACCommand

RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。使用场景:监听按钮点击,网络请求

RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        //input传入的参数,不可返回空信号,[RACSignal empty]也可,执行命令调用该block
        NSLog(@"input====%@",input);//1
        
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:@"执行命令产生的数据"];
            [subscriber sendCompleted];//发送数据完成,一定主动发送完成结束命令执行状态
            return nil;
        }];
    }];
    
//   //底层RACReplaySubject
//    RACSignal *signal = [command execute:@1];
//    [signal subscribeNext:^(id  _Nullable x) {
//        NSLog(@"111%@",x);
//    }];

//    //executionSignals:信号源,信号中信号,signalOfSignals:信号:发送数据就是信号
//    //必须在执行命令前订阅
//    [command.executionSignals subscribeNext:^(id  _Nullable x) {
//        [x subscribeNext:^(id  _Nullable x) {
//           NSLog(@"%@",x);
//        }];
//    }];
//    [command execute:@1];
    
    //switchToLatest:获取信号中信号发送的最新信号
    [command.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        NSLog(@"====%@",x);
    }];
    [command execute:@1];

 // 监听事件有没有完成
    [command.executing subscribeNext:^(id x) {
        if ([x boolValue] == YES)
        {
            NSLog(@"当前正在执行");
        }
        else
        {
            NSLog(@"执行完成/没有执行");
        }
    }];//[command.executing skip:1] subscribeNext//跳过第一步没有执行阶段
  • switchToLatest实现流程
// 创建信号中信号
    RACSubject *signalOfSignals = [RACSubject subject];
    RACSubject *signalA = [RACSubject subject];
    
    // 订阅信号
    //    [signalOfSignals subscribeNext:^(RACSignal *x) {
    //        [x subscribeNext:^(id x) {
    //            NSLog(@"%@",x);
    //        }];
    //    }];
    // switchToLatest:获取信号中信号发送的最新信号
    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];
    
    // 发送信号
    [signalOfSignals sendNext:signalA];
    [signalA sendNext:@1];
9. RAC核心操作方法bind
  • 所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,而RACSignal继承RACStream。
  • 运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术
  • RAC开发方式:RAC中核心开发方式,也是绑定,之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
  • ReactiveCocoa操作的核心方法是bind(绑定),给RAC中的信号进行绑定,只要信号一发送数据,就能监听到,从而把发送数据改成自己想要的数据。
  • 在开发中很少使用bind方法,bind属于RAC中的底层方法,RAC已经封装了很多好用的其他方法,底层都是调用bind,用法比bind简单.
   // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    // 2.绑定信号
    // 参数一(value):表示接收到信号的原始值,还没做处理
    // 参数二(*stop):用来控制绑定Block,如果*stop = yes,那么就会结束绑定。
   // 返回值:信号,做好处理,在通过这个信号返回出去,一般使用RACReturnSignal,需要手动导入头文件RACReturnSignal.h。
    RACSignal *bindSignal =  [subject bind:^RACSignalBindBlock _Nonnull{
       // block调用时刻:只要绑定信号被订阅就会调用
        return ^RACSignal *(id value, BOOL *stop){
            // block调用:只要源信号发送数据,就会调用block
            // 返回信号,不能传nil,返回空信号[RACSignal empty]
            NSLog(@"value:%@",value);
            return [RACReturnSignal return:@"456"];
        };
    }];
    // 3.订阅绑定信号
    [bindSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
    [subject sendNext:@"123"];
//   2019-06-05 17:49:26.338623+0800 RAC[80890:2328714] value:123
//   2019-06-05 17:49:26.338809+0800 RAC[80890:2328714] 456
  • 底层实现:
    1.源信号调用bind,会重新创建一个绑定信号。
    2.当绑定信号被订阅,就会调用绑定信号中的didSubscribe,生成一个bindingBlock。
    3.当源信号有内容发出,就会把内容传递到bindingBlock处理,调用bindingBlock(value,stop)
    4.调用bindingBlock(value,stop),会返回一个内容处理完成的信号(RACReturnSignal)。
    5.订阅RACReturnSignal,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。


    bind流程
10.RAC操作方法之映射:flattenMap,Map

如果信号发出的值是信号,映射一般使用FlatternMap。
如果信号发出的值不是信号,映射一般使用Map。

  • flattenMap:把源信号的内容映射成一个新的信号,信号可以是任意类型。
 //底层bind实现
    RACSubject *subject = [RACSubject subject];
    RACSignal *bindSignal = [subject flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
        //block:只要源信号发送数据就会执行
        //value:源信号发送的内容 返回信号用来包装修改内容值
        return [RACReturnSignal return:value];
    }];
    [bindSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
    [subject sendNext:@"123"];

flattenMap实现:信号中信号取值

 RACSubject *signalOfsignals = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    RACSignal *bindSignal = [signalOfsignals flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
        return value;//源信号发送的内容signal
    }];
    [bindSignal subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];
    // 发送信号
    [signalOfsignals sendNext:signal];
    [signal sendNext:@"213"];
  • map:把源信号的值映射成一个新的值
RACSubject *subject = [RACSubject subject];
    RACSignal *bindSignal = [subject map:^id _Nullable(id  _Nullable value) {
        return [NSString stringWithFormat:@"000:%@",value];
    }];
    [bindSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
    [subject sendNext:@"123"];
11. ReactiveCocoa操作方法之组合。
  • concat: 按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
 RACSignal *siganlA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送上部分请求");
        [subscriber sendNext:@"上部分数据"];
        [subscriber sendCompleted];
        return nil;
    }];
 RACSignal *siganlB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送下部分请求");
        [subscriber sendNext:@"下部分数据"];
        return nil;
    }];
    // concat:按顺序去连接,第一个信号必须要调用sendCompleted
    // 创建组合信号
    RACSignal *concatSignal = [siganlA concat:siganlB];
    // 订阅组合信号
    [concatSignal subscribeNext:^(id x) {
        // 既能拿到A信号的值,又能拿到B信号的值
        NSLog(@"%@",x);
    }];
  • then: 用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
 RACSignal *siganlA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送上部分请求");
        [subscriber sendNext:@"上部分数据"];//被忽略
        [subscriber sendCompleted];
        
        return nil;
    }];
    RACSignal *siganlB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送下部分请求");
        [subscriber sendNext:@"下部分数据"];
        return nil;
    }];
    
    //创建组合信号 then:忽悠掉第一个信号所有值
    RACSignal *thenSiganl = [siganlA then:^RACSignal *{
        // 返回信号就是需要组合的信号
        return siganlB;
    }];
    // 订阅信号
    [thenSiganl subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
//    2019-06-06 14:32:43.158779+0800 RAC[84544:2449482] 发送上部分请求
//    2019-06-06 14:32:43.159032+0800 RAC[84544:2449482] 发送下部分请求
//    2019-06-06 14:32:43.159205+0800 RAC[84544:2449482] 下部分数据
  • merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用.
   RACSubject *signalA = [RACSubject subject];
   RACSubject *signalB = [RACSubject subject];
    // 组合信号
   RACSignal *mergeSiganl = [signalA merge:signalB];
    // 订阅信号
   [mergeSiganl subscribeNext:^(id x) {
        // 任意一个信号发送内容都会来这个block
        NSLog(@"%@",x);
    }];
    // 发送数据
    [signalB sendNext:@"下部分"];
    [signalA sendNext:@"上部分"];
//    2019-06-06 14:36:13.145844+0800 RAC[84608:2451306] 下部分
//    2019-06-06 14:36:13.145975+0800 RAC[84608:2451306] 上部分
  • zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
 // zipWith:夫妻关系
    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];
    // 压缩成一个信号
    // zipWith:当一个界面多个请求的时候,要等所有请求完成才能更新UI
    // zipWith:等所有信号都发送内容的时候才会调用
    RACSignal *zipSignal = [signalA zipWith:signalB];
    // 订阅信号
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    // 发送信号
    [signalB sendNext:@2];
    [signalA sendNext:@1];
    
//    <RACTwoTuple: 0x6000023ef260> (1, 2)
  • combineLatest: 将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
 //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;
12.ReactiveCocoa操作方法之过滤
  • filter:过滤信号,使用它可以获取满足条件的信号.
// 只有当我们文本框的内容长度大于5,才想要获取文本框的内容
    [[_textField.rac_textSignal filter:^BOOL(id value) {
        // value:源信号的内容
        return  [value length] > 5;
        // 返回值,就是过滤条件,只有满足这个条件,才能能获取到内容
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
  • ignore:忽略完某些值的信号.
//ignore:忽略一些值 ignoreValues:忽略所有的值
    RACSubject *subject = [RACSubject subject];
   //RACSignal *ignoreSignal = [subject ignoreValues];
    RACSignal *ignoreSignal = [subject ignore:@"1"];
    // 订阅信号
    [ignoreSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    //发送数据
    [subject sendNext:@"13"];//与1不等可以拿到
  • take:从开始一共取N次的信号
  RACSubject *subject = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    //take:取前面几个值 takeLast:取后面多少个值.必须要发送完成
    //takeUntil:只要传入信号发送完成或者发送任意数据,就不能在接收源信号的内容takeLast:2
    [[subject takeUntil:signal] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [signal sendCompleted];// [signal sendNext:@1];
    [subject sendNext:@"3"];
  • distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
   RACSubject *subject = [RACSubject subject];
    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [subject sendNext:@"1"];
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];//输出1  2
  • skip:(NSUInteger):跳过几个信号,不接受。
RACSubject *subject = [RACSubject subject];
    [[subject skip:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [subject sendNext:@"3"];//3
13. RAC常见宏
  • RAC(TARGET, [KEYPATH, [NIL_VALUE]])
    用于给某个对象的某个属性绑定信号,只要信号产生内容,就把内容给属性赋值。
 // 只要文本框文字改变,就会修改label的文字
    RAC(self.label,text) = _textField.rac_textSignal;
  • RACObserve(self, name):监听某个对象的某个属性,返回的是信号。
  [RACObserve(self.view, frame) subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
  • @weakify(Obj)和@strongify(Obj),一般两个都是配套使用,解决循环引用问题.
  • RACTuplePack:把数据包装成RACTuple(元组类)
    RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。
  // 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@10,@20);
    NSLog(@"%@",tuple[0]);//10
    // name = @"10" age = 20
    RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
    NSLog(@"%@, %@",name, age);
14. RAC常见用法
  • 代替代理:rac_signalForSelector;需要传值:RACSubject
  // 需求:自定义redView,监听红色view中按钮点击
    // 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
    // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
    // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
    [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
    }];
  • 代替KVO
    // 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
    // observer:可以传入nil
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

//rac_valuesForKeyPath
//rac_observeKeyPath//block内处理
  • 监听事件
    // 把按钮点击事件转换为信号,点击按钮,就会发送信号
    [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"按钮被点击了");
    }];
  • 代替通知
 // 4.代替通知
    // 把监听到的通知转换信号
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];
  • 监听文本框
// 监听文本框的文字改变
   [_textField.rac_textSignal subscribeNext:^(id x) {
       NSLog(@"文字改变了%@",x);
   }];
  • 所有信号都发送数据后再执行方法:rac_liftSelector
- (void)testRAC_liftSelector
{
    //保证全部请求完成,再搭建界面
    RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        //请求数据AFN
        NSLog(@"信号1请求");
        [subscriber sendNext:@"信号1数据"];
        return nil;
    }];
    RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        //请求数据AFN
        NSLog(@"信号2请求");
        [subscriber sendNext:@"信号2数据"];
        return nil;
    }];
    //当数组中所有信号都发送数据的时候,才会执行selector
    //方法的参数必须跟数组的信号一一对应,参数就是每个信号发送的数据
    [self rac_liftSelector:@selector(updateUI::) withSignalsFromArray:@[signal1, signal2]];
}

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

推荐阅读更多精彩内容