RAC(ReactiveCocoa)学习之道

摘要:
RAC具有函数式编程和反应式编程的特点,主要吸收了的设计和实现。Net的ReactiveExtensions。事实上,这些事情可以通过RAC来处理。ReactiveCocoa提供了许多时间处理方法,使用RAC处理事件非常方便。您可以将要处理的时间和要监视的时间的代码放在一起,这非常方便我们管理,因此我们不需要调用相应的方法。

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框架,当然,你也可以用终端来导入,看个人喜好。

    RAC(ReactiveCocoa)学习之道第1张RAC(ReactiveCocoa)学习之道第2张

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

RAC(ReactiveCocoa)学习之道第3张

    所以你得在最上面加入   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,敬请期待...

免责声明:文章转载自《RAC(ReactiveCocoa)学习之道》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Linux基础(Ubuntu16.04):安装vim及配置Linux环境下如何生成core文件下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

给11gR2 RAC添加LISTENER监听器并静态注册

之前有同学想要给11gR2的RAC添加LISTENER监听器,查看了listener.ora并发现问题: [oracle@vrh2 ~]$ lsnrctl status LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 04-DEC-2011 02:51:40 Copyright (c) 1991...

Oracle 11g RAC ohasd failed to start at /u01/app/11.2.0/grid/crs/install/rootcrs.pl line 443 解决方法

一.问题描述 在Oracle Linux 6.1 上安装11.2.0.1 的RAC,在安装grid时执行root.sh 脚本,报错,如下: [root@rac1 bin]#/u01/app/11.2.0/grid/root.sh Running Oracle 11g root.sh script... The following environm...

第4步:创建RAC共享磁盘组

第4步:创建RAC共享磁盘组 方法一:使用asmdevices(推荐使用,但不适用EMC Powerpath) (1)查看硬盘的SCSI号,两个机器认到的/dev/sda对应在实际的物理盘可能不是一块,但scsi号肯定是完全一致的。 代码1 [root@sgdb1 ~]# scsi_id -g -u -d /dev/sdc 36000c294cea6...

重新初始化RAC的OCR盘和Votedisk盘,修复RAC系统

假设我们的RAC环境中OCR磁盘和votedisk磁盘全部被破坏,并且都没有备份,那么我们该如何恢复我们的RAC环境。最近简单的办法就是重新初始化我们的ocr盘和votedisk盘,把集群中的所有相关资源重新注册到OCR磁盘和votedisk磁盘中。 1.停掉所有节点的Clusterware Stack [root@rac3 bin]# ./crsctl...

11g R2 RAC 虚拟机

虚拟机安装RAC文档 本文档包含内容 一:安装系统 二:各节点配置系统参数 三:虚拟机创建共享存储 四:配置磁盘绑定 五:安装GRID 六:创建ASM DG 七:安装database 八:安装碰到的问题 (提示:在上述内容按ctrl加鼠标左键可跳转到对应位置) 一:安装系统 两台虚拟主机配置(网卡1公有IP,网卡2私有IP) 1安装选项,默认安装即可...

Oracle Data Guard 重要配置参数

Oracle Data Guard主要是通过为生产数据库提供一个或多个备用数据库(是产生数据库的一个副本),以保证在主库不可用或异常时数据不丢失并通过备用数据库继续提供服务。对于Oracle DG的配置,我们可以通过Grid Control来完成,也可以通过Data Guard Broker以及SQL*Plus来完成。对于前两者方式可以在图形界面上完成,操...