RAC(ReactiveCocoa)学习之道

1、ReactiveCocoa简介

  ReactiveCocoa(简称RAC),是由Github开源的一个应用于iOS和iOS开发的新框架。Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。RAC具有函数式编程和响应式编程的特性,主要吸取了 .Net 的 Reactive Extensions 的设计和实现。

2、ReactiveCocoa作用

  在我们iOS开发过程中,经常会响应某些事件来处理某些业务逻辑,例如按钮的点击,上拉刷新,网络请求,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback(回调)等。

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

3、编程思想

  在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没法维护,比如之前Facebook提供的 three20框架,在当时也是神器,但是后来不更新了,也就没有什么人用了。因此学习一个框架,还是有必要了解它的 编程思想

  先简单介绍下目前已知的编程思想。

  3.1、面向过程:处理时间以过程为核心,一步一步的实现。

  3.2、面向对象:万物皆对象

  3.3、链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码使代码可读性好。a(1).b(2).c(3)

    • 链式编程特点:方法的返回值必须是block,block必须有返回值(本身对象,或者可以叫方法调用者),block参数(需要操作的值或者内容)
    • 代表:masonry框架 
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    
    // mas_makeConstraints: 作用:给控件设置布局
    // - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block
    // 1、创建一个约束制造者
    // 2、调用block(maker)
    // 3、[constraintMaker install]: 遍历约束制造者的所有约束控件添加约束
    
    // 参数: block
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        // 描述控件的约束
        
        // 上下左右间距都为10
        // make.left -> NASViewConstraint
        // make.left.top:把左边和顶部的约束全部保存到make.contrains
        
        // equalTo:方法
        // equalTo返回值: block
        make.left.top.equalTo(@10);
        make.right.bottom.equalTo(@(-10));
    }];

    下面我们通过写一个简单的计算器来学习一下链式编程思想。

    首先,我们需要创建一个继承NSObject的类,命名为CalculatorMaker,然后在 .h 文件中声明一个计算结果的属性跟一个加法计算的方法,返回值为一个block。

@property (nonatomic, assign) int result;

/** 相加 */
- (CalculatorMaker *(^)(int num))add;

    然后我们需要在 .m 文件中把这个相加的方法实现。

- (CalculatorMaker * (^)(int num))add {
    return ^(int num) {
        _result += num;
        return self;
    };
}

    这样,一个加法计算的方法就完成了,然后在ViewController中导入头文件,并在viewDidLoad中使用这个方法。

    // 1、创建计算制造者
    CalculatorMaker *maker = [[CalculatorMaker alloc] init];
    
    // 链式编程思想:maker.add(10).add(20).add(30).add(40);
    // 提供一个没有参数的add方法,返回值block
    
    int result = [maker.add(10).add(20).add(30).add(40) result];
    NSLog(@"result = %@", @(result));

    但是这样实现跟第三方Masonry实现的方法不太一样,所以接下来我们需要再写一个类,把计算器中的加、减、乘、除方法的调用整合到一起,用一个block就能实现这些功能。首先创建一个分类,继承NSObject,命名为Caculator,因为这个方法是继承自NSObject的,所以我们要先导入头文件 #import "CalculatorMaker.h", 然后在 .h 中我们先声明一个方法,以后计算都可以直接受用这个方法,一调用这个方法就返回结果。模仿着Masonry的实现方法,我们自己写一个方法。

// 以后计算都使用这个方法,一调用这个方法就返回结果
+ (int)makeCalculator:(void (^)(CalculatorMaker *))block;

    方法声明后,肯定是要实现 TA 的。下面便是实现 TA 方法的代码。返回值为 int 类型的,因为你要返回的是一个结果。

+ (int)makeCalculator:(void (^)(CalculatorMaker *))block {
    // 创建计算制造者
    CalculatorMaker *maker = [[CalculatorMaker alloc] init];
    // 计算
    block(maker);
    return maker.result;
}

    有了这个方法,那么计算器的使用就会更加的简便。下面就让我们来看看怎么实现 TA。回到 viewController.m 文件中,导入头文件 #import "NSObject+Calculator.h",然后我们就可以实现高聚合的block方法了。

int result = [NSObject makeCaculator:^(CalculatorMaker *maker) {
    // 把所有的计算代码封装到这里
    maker.add(10).add(20);
    maker.add(30).add(40);
}];
NSLog(@"result = %@", @(result));

    上面的代码输出的结果跟之前的一样,但是这样使用要比之前的使用更好,跟Masonry一样,一个block方法实现所有的功能。

    好了,链式编程思想就讲到这里了,后面深入就需要自己研究了。 

    完整代码传送门

  3.4、响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。

    举个例子,如果你要求 c = a + b; 那么你必须得知道 a 跟 b 的值,先定义 a 跟 b,并给他们赋值,这是正常的思路。但是响应式编程的话,你可以先定义 a 跟 b 的值,然后让 c = a + b,在后面给 a 跟 b 赋值,把 a 跟 b 与 c 绑定了,赋值后 c 的值也会相应的改变。这就是响应式编程思想,经过上面一个小例子,相比大家应该能理解上面那句不需要考虑调用顺序,只需要考虑结果这句话的意思了吧。

    • 代表:KVO运用

   下面我们来看一下KVO的实现,首先创建一个工程,然后创建一个类(Person)继承自NSObject,然后在 .h 文件里面声明一个年龄(age)属性。

    /** 年龄 */
    @property (nonatomic, assign) NSInteger age;

    然后回到viewController.m 文件,导入头文件,声明一个Person,并在viewDidLoad创建 TA,给 TA 添加KVO,并把方法实现,重写touchesBegan:withEvent:

每次点击屏幕让Person类中的age加1。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _p = [[Person alloc] init];
    
    // 添加观察者
    [_p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
}

/**
 *  监听的属性只要一次改变就调用
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    NSLog(@"%@", @(_p.age));
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    _p.age++;
}

    写完之后,如果你没有写错的话,那么你运行完之后点击空白的地方,能在控制台看到age每输出一次就增加一次。但是如果你把 _p.age++ 改成 _p -> _age++,然后在Person类中添加如下代码,再次运行,点击空白处,你就会发现控制台不会再输出age了。

@interface Person : NSObject
{
    @public
    NSInteger _age;
}

    从上面的例子你就能发现,KVO的底层实现就是去判断有没有调用一个对象的set方法。如果你想更底层的研究,就需要使用runtime来拦截方法,这里就不介绍了,有个demo的传送门,想要了解的看下。

  3.5、函数式编程思想:是把操作尽量携程一系列嵌套的函数或者方法调用。

    • 函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数 block参数 (需要操作的值) block返回值 (操作结果)
    • 代表:ReactiveCocoa

      下面我们来写一个计算器并且有判断功能的小demo。首先,创建一个集成NSObject的类Calculator,因为既能计算,又能判断值是否相等,所以我们得先声明两个属性,一个是记录计算的结果,一个是判断两个数是否相等。

/** 记录结果 */
@property (nonatomic, assign) NSInteger result;
/** 判断是否相等 */
@property (nonatomic, assign) BOOL isEqual;

      然后声明两个方法,一个是加法计算,一个是判断两个值是否相等。

/**
 *  加法
 */
- (instancetype)add:(NSInteger(^)(NSInteger result))block;

/**
 *  判断两个值是否相等
 */
- (instancetype)equal:(BOOL(^)(NSInteger result))block;

     到 .m 文件中去实现这两个方法,因为函数值编程思想是把block或者函数作为参数,操作的结果作为返回值,所以,它的实现方法就是这样的。

- (instancetype)add:(NSInteger(^)(NSInteger result))block {
    _result = block(_result);
    return self;
}

- (instancetype)equal:(BOOL (^)(NSInteger))block {
    _isEqual = block(_result);
    return self;
}

    既然方法都写完了,回到ViewController里面,我们来使用一下 TA 看看。下面是加法计算获得值的使用。

    Calculator *calculator = [[Calculator alloc] init];
    
    NSInteger result = [[calculator add:^(NSInteger result) {
        result += 10;
        result += 20;
        result += 30;
    
        return result;
    }] result];
    NSLog(@"%ld", result);

    然后我们加入判断的方法,再看看是如何使用这个的。

    BOOL isEqual = [[[calculator add:^(NSInteger result) {
        result += 10;
        result += 20;
        result += 30;
        
        return result;
    }] equal:^BOOL(NSInteger result) {
        return result == 600;
    }] isEqual];
    NSLog(@"%d", isEqual);

 

4、ReactiveCocoa编程思想

    ReactiveCocoa结合了几种编程风格:

  •     函数式编程(Functional Programming)
  •     响应式编程(Reactive Programming)

    所有,ReactiveCocoa会被描述为函数响应式编程(FRP)框架。

    以后解决问题就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。

 

5、如何导入ReactiveCocoa框架

    通常会使用CocoaPods(管理第三方框架的插件)来导入ReactiveCocoa框架,当然,你也可以用终端来导入,看个人喜好。

    

    如果你直接按照上面这样导入的话,就会报下面的错误。

    所以你得在最上面加入   use_frameworks!  这句话,这样,才能完成ReactiveCocoa框架的导入。

 

6、ReactiveCocoa常见类()

    学习框架首要之处:得先搞清楚框架中常用的类,在RAC中最核心的类RACSiganl,搞定了这个类就能基本使用ReactiveCocoa开发了。

    RACSiganl:信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。

    •  信号类(RACSiganl):只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。
    • 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。
    • 如何订阅信号:调用信号RACSignal的subscribeNext就能订阅。
    • RACSiganl简单实用:
  • // RACSignal使用步骤: // 1、创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe // 2、 发送信号 - (void)sendNext:(id)value // 3、订阅信号,才会激活信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock // RACSignal底层实现: // 1、创建信号,首先把didSubscribe保存到信号中,还不会触发 // 2、当信号被订阅,也就是调用signal的subscribeNext:nextBlock // 2.1 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中 // 2.2 subscribeNext内部会调用siganl的didSubscribe // 3、siganl的didSubscribe中调用[subscriber sendNext:@1]; // 3.1 sendNext底层其实就是执行subscriber的nextBlock

 

    下面是创建的方法。

    // 1.创建信号 createSignal:didSubscribe(block)
    // RACDisposable:取消订阅
    // RACSubscriber:发送数据
    
    // createSignal方法:
    // 1.创建RACDynamicSignal
    // 2.把didSubscribe保存到RACDynamicSignal
    
    RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // block调用时刻:当信号被订阅的时候就会调用
        // block作用:描述当前信号哪些数据需要发送
        //       _subscriber = subscriber;
        // 发送数据
        NSLog(@"调用了didSubscribe");
        // 通常:传递数据出去
        [subscriber sendNext:@1];
        // 调用订阅者的nextBlock
        
        // 如果信号,想要被取消,就必须返回一个RACDisposable
        return [RACDisposable disposableWithBlock:^{
            
            // 信号什么时候被取消:1.自动取消,当一个信号的订阅者被销毁的时候,就会自动取消订阅 2.主动取消
            // block调用时刻:一旦一个信号,被取消订阅的时候就会调用
            // block作用:当信号取消订阅,用于清空一些资源
            NSLog(@"取消订阅");
        }];
    }];
    
    // subscribeNext:
    // 1.创建订阅者
    // 2.把nextBlock保存到订阅者里面
    // 订阅信号
    // 只要订阅信号,就会返回一个取消订阅信号的类
    RACDisposable *disposable = [siganl subscribeNext:^(id x) {
        
        // block:只要信号内部发送数据,就会调用这个block
        NSLog(@"%@",x);
    }];
    
    // 取消订阅
    [disposable dispose];
  • RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个雷,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
  • RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发 TA。
    • 使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
  • RACSubject:信号提供者,自己可以充当信号,又能发送信号。
    • 使用场景:通常用来代替代理,有了 TA,就不必要定义代理了。
  • RACReplaySubject:重复提供信号类,RACSubject的子类。
  • RACReplaySubject 与 RACSubject 区别:
    • RACReplaySubject可以先发送信号,在订阅信号,RACSubject就不可以。
    • 如果一个信号被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。
    • 可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。

 

  • RACSubject 和 RACReplaySubject简单使用:
  • // RACSubject使用步骤
        // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
        // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        // 3.发送信号 sendNext:(id)value
    
        // RACSubject:底层实现和RACSignal不一样。
        // 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
        // 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
    
        // 1.创建信号
        RACSubject *subject = [RACSubject subject];
    
        // 2.订阅信号
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第一个订阅者%@",x);
        }];
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第二个订阅者%@",x);
        }];
    
        // 3.发送信号
        [subject sendNext:@"1"];
    
    
        // RACReplaySubject使用步骤:
        // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
        // 2.可以先订阅信号,也可以先发送信号。
        // 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        // 2.2 发送信号 sendNext:(id)value
    
        // RACReplaySubject:底层实现和RACSubject不一样。
        // 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
        // 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
    
        // 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
        // 也就是先保存值,在订阅值。
    
        // 1.创建信号
        RACReplaySubject *replaySubject = [RACReplaySubject subject];
    
        // 2.发送信号
        [replaySubject sendNext:@1];
        [replaySubject sendNext:@2];
    
        // 3.订阅信号
        [replaySubject subscribeNext:^(id x) {
    
            NSLog(@"第一个订阅者接收到的数据%@",x);
        }];
    
        // 订阅信号
        [replaySubject subscribeNext:^(id x) {
    
            NSLog(@"第二个订阅者接收到的数据%@",x);
        }];

     

  • RACSubject替换代理
    1. 给当前控制器添加一个按钮,modal到另一个控制器界面
    2. 另一个控制器view中有个按钮,点击按钮,通知当前控制器
  1. 创建一个控制器FirstViewController一个控制器SecondViewController都继承UIViewController,然后在 SecondViewController.h 里面添加一个RACSubject代替代理。当然,你得先导入头文件。#import "ReactiveCocoa.h"
@interface SecondViewController : UIViewController

@property (nonatomic, strong) RACSubject *delegateSignal;

@end

      2. 在 SecondViewController.m 中实现一个点击按钮的事件。

- (IBAction)secondButton:(id)sender {
    // 通知第一个控制器,告诉它,按钮被点了
    
    // 通知代理
    // 判断代理信号是否有值
    if (self.delegateSignal) {
        // 有值,才需要通知
        [self.delegateSignal sendNext:nil];
    }
}

  3. 在 FirstViewController.m 中也实现一个按钮的点击事件。

- (IBAction)firstButton:(id)sender {
    // 创建第二个控制器
    SecondViewController *secondVC = [[SecondViewController alloc] init];
    
    // 设置代理信号
    secondVC.delegateSignal = [RACSubject subject];
    
    // 订阅代理信号
    [secondVC.delegateSignal subscribeNext:^(id x) {
        NSLog(@"点击了通知按钮");
    }];
    
    // 跳转到第二个控制器
    [self presentViewController:secondVC animated:YES completion:nil];
}

    这样,你就完成了使用RACSubject 替换代理的方法。

  • RACTuple:元祖类,类似于NSArray,用来包装值。
  • RACSequence:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
    • 使用场景:1、字典转模型
    • RACSequence和RACTuple简单使用。
    // 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];
  • RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,TA 可以很方便的监控事件的执行过程。
    • 使用场景:监听按钮点击,网络请求
    • RACCommand简单使用
     // 一、RACCommand使用步骤:
    // 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
    // 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
    // 3.执行命令 - (RACSignal *)execute:(id)input

    // 二、RACCommand使用注意:
    // 1.signalBlock必须要返回一个信号,不能传nil.
    // 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
    // 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
    // 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。

    // 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
    // 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
    // 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。

    // 四、如何拿到RACCommand中返回信号发出的数据。
    // 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
    // 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。

    // 五、监听当前命令是否正在执行executing

    // 六、使用场景,监听按钮点击,网络请求


    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {


        NSLog(@"执行命令");

        // 创建空信号,必须返回信号
        //        return [RACSignal empty];

        // 2.创建信号,用来传递数据
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

            [subscriber sendNext:@"请求数据"];

            // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
            [subscriber sendCompleted];

            return nil;
        }];

    }];

    // 强引用命令,不要被销毁,否则接收不到数据
    _conmmand = command;



    // 3.订阅RACCommand中的信号
    [command.executionSignals subscribeNext:^(id x) {

        [x subscribeNext:^(id x) {

            NSLog(@"%@",x);
        }];

    }];

    // RAC高级用法
    // switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];

    // 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
    [[command.executing skip:1] subscribeNext:^(id x) {

        if ([x boolValue] == YES) {
            // 正在执行
            NSLog(@"正在执行");

        }else{
            // 执行完成
            NSLog(@"执行完成");
        }

    }];
   // 5.执行命令
    [self.conmmand execute:@1];
  • RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
    • RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建
    • RACMulticastConnection简单使用:
    // RACMulticastConnection使用步骤:
    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
    // 2.创建连接 RACMulticastConnection *connect = [signal publish];
    // 3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock]
    // 4.连接 [connect connect]

    // 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就能解决.

    // 1.创建请求信号
   RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {


        NSLog(@"发送请求");

        return nil;
    }];
    // 2.订阅信号
    [signal subscribeNext:^(id x) {

        NSLog(@"接收数据");

    }];
    // 2.订阅信号
    [signal subscribeNext:^(id x) {

        NSLog(@"接收数据");

    }];

    // 3.运行结果,会执行两遍发送请求,也就是每次订阅都会发送一次请求


    // RACMulticastConnection:解决重复请求问题
    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {


        NSLog(@"发送请求");
        [subscriber sendNext:@1];

        return nil;
    }];

    // 2.创建连接
    RACMulticastConnection *connect = [signal publish];

    // 3.订阅信号,
    // 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
    [connect.signal subscribeNext:^(id x) {

        NSLog(@"订阅者一信号");

    }];

    [connect.signal subscribeNext:^(id x) {

        NSLog(@"订阅者二信号");

    }];

    // 4.连接,激活信号
    [connect connect];
  • RACScheduler:RAC中的队列,用GCD封装的。
  • RACUnit:表示stream不包含有意义的值,也就是看到这个,可以直接理解为nil
  • RACEvent:把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用。

7、ReactiveCocoa开发中常见用法

  • 代替代理
    • rac_signalForSelector:用于代替代理
  • 代替KVO
    • rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
  • 监听事件
    • rac_signalForControlEvents:用于监听某个事件
  • 代替通知:
    • rac_addObserverForName:用于监听某个通知
  • 监听文本框文字改变
    • rac_textSignal:只要文本框发出改变就会发出这个信号
  • 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
    • rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会触发第一个selector参数的方法
    • 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据
  • 代码演      // 1.代替代理    // 需求:自定义redView,监听红色view中按钮点击
// 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
    // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
    // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
    [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
    }];

    // 2.KVO
    // 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
    // observer:可以传入nil
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {

        NSLog(@"%@",x);

    }];

    // 3.监听事件
    // 把按钮点击事件转换为信号,点击按钮,就会发送信号
    [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {

        NSLog(@"按钮被点击了");
    }];

    // 4.代替通知
    // 把监听到的通知转换信号
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];

    // 5.监听文本框的文字改变
   [_textField.rac_textSignal subscribeNext:^(id x) {

       NSLog(@"文字改变了%@",x);
   }];

   // 6.处理多个请求,都返回结果的时候,统一做处理.
    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;
    }];

    // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
    [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
}
/**
*
更新UI
*/
- (void)updateUIWithR1:(id)data r2:(id)data1 { NSLog(@"更新UI%@ %@",data,data1); }

 

8、ReactiveCocoa常见宏

  • RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定。
  •  // 只要文本框文字改变,就会修改label的文字
        RAC(self.labelView,text) = _textField.rac_textSignal;

     

  • RACObserve(self, name):监听某个对象的某个属性,返回的是信号
  •    [RACObserve(self.view, center) subscribeNext:^(id x) {
    
            NSLog(@"%@",x);
        }];

     

  • @weakify(obj) 和 @strongify(obj),一般两个都是配套使用,在主头文件(ReactiveCocoa.h)中并没有导入,需要自己手动导入,RACEXTcope.h 才可以使用。但是每次导入都非常麻烦,只需要在猪头文件自己导入就好了。
  • RACTuplePack:把数据包装成RACTuple(元祖类)
  •     // 把参数中的数据包装成元组
        RACTuple *tuple = RACTuplePack(@10,@20);

     

  • RACTupleUnpack:把RACTuple(元祖类)解包成对应的数据。
  •     // 把参数中的数据包装成元组
        RACTuple *tuple = RACTuplePack(@"xmg",@20);
    
        // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
        // name = @"xmg" age = @20
        RACTupleUnpack(NSString *name,NSNumber *age) = tuple;

     

  • 后续还会研究一段时候后的小demo,敬请期待...

posted on 2016-05-12 18:02  小鸟成长记  阅读(577)  评论(0编辑  收藏  举报

导航