ReactiveCocoa基本使用详解

简介:

ReactiveCocoa(简称为RAC)被称为函数响应式编程框架FRP(Functional Reactive Programming),是GitHub开源的。


作用:

开发中经验会用到按钮事件Action、代理delegate、监听属性变化KVO、通知、网络请求等,这些常见的操作都是将监听和实现分离开来的,读代码时需要来回跳跃,降低代码的可读性,使用RAC可以使监听和实现写在一起,使代码更加紧凑,也就是“高聚合、低耦合”思想。


编程思想:

要想了解RAC,先来了解几个编程思想:链式编程、函数式编程、响应式编程

链式编程思想:

在Objective-C开发中调用方法都是使用中括号 []来调用,层层嵌套,如 [ [ [XXView alloc] init ] addSubview:view]; 使用中括号调用方法这种方式感觉非常奇葩,要是嵌套多层这代码的可读性不敢想象!像其他大部分编程语言(如Java等)调用方法都是通过点语法来完成的,例如 XXView.alloc().init().addSubview(view); 这样调用感觉很清新,比通过中括号[] 一层一层嵌套的清爽多了。

那么在Objective-C中能否使用点语法来调用方法呢???


答案是可以的,但是方法的签名的格式是固定的:

  • 首先方法是 getter型方法
  • getter方法的返回值是方法的调用者
    • 可以直接返回方法的调用者
    • 也可以返回一个Block,Block的返回值是调用者,这样通过执行Block返回值最终也能拿到方法的调用者,Block的参数是要操作的值,但不是必须的

为什么是getter型方法???


我们在Objective-C中使用到点语法好像只有在获取某个对象的某个属性时才会用到点语法, 例如: self.view 、 self.view.backgroundColor 等,链式编程要想成为可能也必须向调用对象的属性这种方式靠拢,而 self.view 其实是[self view],也就是调用self 对象的view方法,而这个方法就是getter方法


方法的返回值为什么是方法的调用者???

如果方法的返回值不是方法的调用者,那么链式编程就没办法使用点语法继续写了,因为点不出来东西了


链式编程:就是将多个方法调用通过点语法连成一串,形成一行代码,这样使得代码的可读性更高,例如 xxx.a(1).b(2).c(3) ,链式编程思想的代表作是自动布局masonry框架:make.top.left.equalTo(@10);

masonry 示例代码:

- (void)viewDidLoad {
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView]; //使用masonry必须先添加子视图后设置约束

    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.equalTo(@10);
        make.bottom.right.equalTo(@(-10));
    }];
}

// 查看masonry框架源码可得知:top、left、bottm、right 方法的返回值都是直接返回的约束对象:MASConstraint *, equalTo方法的返回值是一个Bolok:MASConstraint * (^)(id),该Block的最终返回值也是约束对象:MASConstraint *

Masonry框架源码:

分类:UIView+MASAdditions

@implementation MAS_VIEW (MASAdditions)

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
   // 1.创建约束制造对象
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];

    // 2.执行代码块
    block(constraintMaker);

    // 3.返回结果
    return [constraintMaker install];
}
@end

约束类:MASConstraint

// 方法的返回值:MASConstraint * 
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

// 方法的返回值: MASConstraint * (^)(id)
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

模仿Masonry框架写一个链式编程的计算器:

Calculator : 计算器

@interface Calculator : NSObject

typedef Calculator*(^CalcBlock)(int x);
@property (assign, nonatomic) CGFloat result;

- (CalcBlock)add;   // 加
- (CalcBlock)sub;   // 减
- (CalcBlock)multi; // 乘
- (CalcBlock)div;   // 除

@end


#import "Calculator.h"
// 计算器
@implementation Calculator

- (CalcBlock)add {

    return ^(int x){
        _result = _result + x;

        return self;
    };
}

- (CalcBlock)sub {
    return ^(int x){
        _result = _result - x;

        return self;
    };
}

- (CalcBlock)multi {
    return ^(int x){
        _result = _result * x;

        return self;
    };
}

- (CalcBlock)div {
    return ^(int x){
        _result = _result / x;

        return self;
    };
}
@end

分类:NSObject+XXAdditions

#import 
#import "Calculator.h"

@interface NSObject (XXAdditions)
- (int)xx_makeCalculate:(void(^)(Calculator *))block;
@end


#import "NSObject+XXAdditions.h"
@implementation NSObject (XXAdditions)
- (int)xx_makeCalculate:(void(^)(Calculator *))block {
    Calculator *calculator = [[Calculator alloc] init];

    block(calculator);

    return calculator.result;
}

@end

main.m

#import 
#import "NSObject+XXAdditions.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        NSObject *obj = [[NSObject alloc] init];
        CGFloat result = [obj xx_makeCalculate:^(Calculator *calculator) {
            // 20 - 10 = 10; 10 * 3 = 30; 30/2 = 15.0;
            calculator.add(20).sub(10).multi(3).div(2); 
        }];

        NSLog(@"%f", result);
    }
    return 0;
}

使用链式编程调用方式: calculator.add(20).sub(10).multi(3).div(2);
使用OC传统方式: [ [ [ [calculator add:20] sub:10] multi:3] div:2];
看到那么多中括号简直是不能直视,通过比较可以得出链式编程使代码的可读性更高,使得代码更加简洁



函数式编程思想:

函数式编程:就是尽可能的使用函数嵌套的方式来编程,函数式编程的目的就是尽可能的使代码高内聚,代表框架是ReactiveCocoa

函数式编程的方法签名要求:

  • 方法的返回值是对象本身
  • 方法的参数是Block
    • Block的参数是需要操作的值
    • Block的返回值是操作的结果

例如: - (instancetype)calc:(int(^)(int x))block;

使用函数式编程来编写个计算器功能:

Calculator :计算器类

#import 

@interface Calculator : NSObject

@property (assign, nonatomic) CGFloat result;  // 记录操作结果

- (instancetype)calc:(int(^)(int x))block;

@end


#import "Calculator.h"
// 计算器
@implementation Calculator
- (instancetype)calc:(int(^)(int x))block {
    _result = block(_result);

    return self;
}
@end

main.m

#import 
#import "Calculator.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Calculator *calculator = [[Calculator alloc] init];
        CGFloat result = [[calculator calc:^int(int x) {
            x += 10;
            x -= 2;
            x *= 2;
            x /= 8;

            return x;  // 返回处理后的数据
        }] result];

        NSLog(@"%f", result);
    }
    return 0;
}

// 方法的返回值是自己:instancetype, 方法的参数是一个代码块,代码块中的参数是要操作的值,代码块的返回值是操作的结果, 因返回值是本身,所以还可以再次调用result属性,使得代码更加紧凑,高内聚。

响应式编程思想:

响应式编程是一种面向【数据流】和【变化传播】的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

在响应式编程中万物皆是流。 流廉价且无处不在,任何事物都可以当作一个流:变量、用户输入、属性、缓存、数据结构等等

例如,在命令式编程环境中,float a = b + c;表示将表达式b+c的结果赋给a,而之后改变b或c的值是不会影响a的结果的。但在响应式编程中,a的值会随着b或c的更新而更新。

电子表格程序Excel就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1”的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

在Web开发中现在流行一种响应式布局,就是页面的大小发生变化,页面中的内容也要随之自动缩放。

在MVC软件架构中,响应式编程允许将相关模型的变化自动反映到视图上,反之亦然。

RAC基本介绍

一: 集成ReactiveCocoa

  1. 集成ReactiveCocoa, pod install –verbose –no-repo-update

    ReactiveCocoa基本使用详解_第1张图片

选择Later
ReactiveCocoa基本使用详解_第2张图片

编译后报错

ReactiveCocoa基本使用详解_第3张图片

修改Pods中的TARGETS中的RactiveCocoa–>Build Settings—->User Legacy Swift Language Version: YES 即可,重新编译Success
ReactiveCocoa基本使用详解_第4张图片


二: 初窥ReactiveCocoa

示例1:RACSignal :只能订阅信号,在内部由订阅者发送信号

注意在导入#import “ReactiveCocoa.h”主头文件时Xcode不自动提示的,强行导入,然后编译即可使用,先不说概念,上来先写一个示例

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // 1. 创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { // 订阅信号,执行subscribeNext方法时才会调用该代码块
        // 3. 发送信号
        [subscriber sendNext:@"123"];
        [subscriber sendCompleted];

        return [RACDisposable disposableWithBlock:^{
            NSLog(@"当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。执行完Block后,当前信号就不在被订阅了。。。");
        }];
    }];

    // 2.订阅信号
    [signal subscribeNext:^(id x) {  // 发送数据:即调用sendNext时才会调用该代码块
        NSLog(@"接收数据: %@", x); // 接收数据: 123
    }];
}

@end

代码解释:

  • 信号使用的三部曲:创建信号、订阅信号、发送Value
  • 创建信号时传入的代码块参数称为:didSubscribe(已经订阅)
  • 订阅信号时传入的代码块参数称为:nextBlock(下一个代码块)
  • 上面示例只有执行subscribeNext订阅下一个方法时才会执行订阅时的didSubscribe代码块, 只有订阅信号,信号才会变成热信号
  • 上面示例只有执行sendNext方法时才会执行nextBlock代码块
  • 发送Value,值变化了,也就是流中的数据发生变化了,产生新的信号了
  • RACDisposable 类中关联的block称之为disposeBlock 处理代码块,取消订阅时调用,用于清理资源等操作

代码中用到的类

RACSignal
信号类继承自RACStream(流),这里写图片描述,所以信号类也是一种流,在响应式框架中一切皆是流,该类是RAC框架中最基础,最核心的类,在内部可以使用订阅者来发送 id值value、错误error、完成completed。

没有被订阅的信号称为 【冷信号】,被订阅的信号称之为【热信号】,只有信号被订阅了,同时产生新的信号(sendNext)时才会执行nextBlock。

RACSignal createSignal 其实返回的是RACDynamicSignal类,是RACSignal的子类,这里写图片描述

如果取消订阅时不需要做任何处理那么createSignal时直接返回nil即可

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"123"];

        return nil; // 返回nil
}];

RACSubscriber
订阅者协议这里写图片描述,遵守该协议并实现协议中的方法都可以称为一个订阅者。

RACSubscriber 协议:

@protocol RACSubscriber <NSObject>
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end

信号是用来被订阅的,也就是被观察的,一但数据流中的数据发生变化就能被观察到,一但观察到就会执行关联的nextBlock代码块,订阅者可以发送任意值Value、错误error、完成completed 等变化来传播

ReactiveCocoa基本使用详解_第5张图片

RACDisposable :
取消订阅,当订阅者RACSubscriber没有强引用销毁了,或者发送错误sendError,或者发送完成sendCompleted时就会执行RACDisposable关联的处理代码块:disposeBlock ,默认一个信号发送完毕就会取消订阅, 只要订阅者还存在系统就不会自动取消订阅,所以可以通过一个全局变量来来强引用订阅者,这样系统就不会自动执行取消订阅的代码块

当不想监听某个信号时,可以通过调用dispose方法来主动取消订阅信号。

#import "ViewController.h"
#import "ReactiveCocoa.h"

@interface ViewController ()
@property (strong, nonatomic) id subscriber;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // 1. 创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        // 3. 发送信号
        [subscriber sendNext:@"123"];

        _subscriber = subscriber;  // 全局变量保存起来,系统就不会自动取消订阅
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"取消订阅,清理资源");
        }];
    }];

    // 2.订阅信号
    RACDisposable *dispose = [signal subscribeNext:^(id x) {
        NSLog(@"接收数据: %@", x);
    }];

    [dispose dispose]; // 主动取消订阅
}

@end

实例1代码流程图

ReactiveCocoa基本使用详解_第6张图片

  1. 第一步:创建信号:该方法将didSubscribe代码块保存到RACDynamicSignal中的didSubscribe属性中
  2. 第二步:订阅信号:将nextBlock保存到订阅者中RACSubscriber中的next代码块属性中,并且调用RACDynamicSignal中的didSubscribe()真正订阅;
  3. 第三步:由于第二步调用了didSubscribe()代码块,所以接着执行代码块中的sendNext: 函数
  4. 第四步:sendNext函数会调用RACSubscriber中的next代码块,这样流程就走完了

示例2:RACSubject :既能订阅信号,也能发送信号

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // 1. 创建信号
    RACSubject *subject = [RACSubject subject];

    // 2. 订阅信号
    [subject subscribeNext:^(id x) {
        NSLog(@"接收到值:%@", x);
    }];

    // 3. 发送信号
    [subject sendNext:@"123"];
}

@end

代码解释

RACSubject :
该类继承自RACSignal,同是也遵守了RACSubscriber订阅者协议,所以该类既能订阅信号,同时也能发送信号;这里写图片描述在上个示例中发送信号是由内部代码块中的订阅者完成的。RACSubject 的应用场景一般是取代代理来使用

示例代码2的底层流程图

ReactiveCocoa基本使用详解_第7张图片

  • 第一步:创建信号,仅仅是创建一个空的可变数组subscribers,用于保存所有的订阅者
  • 第二步:订阅信号,创建一个订阅者并关联对应的nextBlock,然后将订阅者添加到可变数组中
  • 第三步:发送信号,循环遍历订阅者数组,调用每个订阅者对应的nextBlock代码块

因为订阅者是数组,所以使用RACSubject可以进行多次订阅

ReactiveCocoa基本使用详解_第8张图片


示例3:RACReplaySubject :即可以先订阅信号再发送信号,也可以先发送信号再订阅信号

先订阅再发送:

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // 1. 创建信号
    RACReplaySubject *replaySubject = [RACReplaySubject subject];

    //  订阅信号
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"订阅者1接收到值:%@", x);
    }];

    // 发送信号
    [replaySubject sendNext:@"123"];
}
@end

先发送再订阅:

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // 1. 创建信号
    RACReplaySubject *replaySubject = [RACReplaySubject subject];

    // 发送信号
    [replaySubject sendNext:@"123"];

    // 订阅信号
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"订阅者1接收到值:%@", x);
    }]; 
}
@end

RACReplaySubject 类:
该类继承子RACSubject类:这里写图片描述,该类的特别之处是订阅信号和发送信号的顺序没有要求,可以先订阅信号,再发送信号;也可以先发送信号再订阅信号。

RACReplaySubject 的底层代码执行

ReactiveCocoa基本使用详解_第9张图片

底层代码流程图解释

  • 第一步:创建信号,该类继承自RACSubject,初始化时创建订阅者数组subscribers,子类自己在初始化时也创建了一个接收值的可变数组valuesReceived
  • 订阅信号:创建订阅者并绑定对应的代码块nextBlock,然后调用自己类的订阅方法subscribe,该方法会首先遍历接收值数组valuesReceived,如果该值为空跳过循环,如果该值不为空,就遍历所有接收值发送到当前订阅者对象,然后将该订阅者保存到订阅者数组subscribers中,如果是先订阅后发送,因为接收值数组valuesReceived为空,所以不会发送数据;如果是先发送数据后订阅,由于先发送数据,数据首先会保存起来,然后再遍历接收值数组valuesReceived数组,将每个接收值遍历的发送到当前订阅者对象中
  • 发送信号:首先将发送的值保存到valuesReceived数组中,然后再调用父类的发送方法sendNext,父类方法会遍历所有订阅者逐一去发送当前值
  • 如果是先订阅后发送:处理方法是遍历所有的订阅者数组subscribers,将当前值逐一发送给每一个订阅者;如果是先发送后订阅:处理方法是遍历所有接收值数组valuesReceived,将每个值逐一发送给当前订阅者;订阅和发送的顺序不同,处理逻辑是反着的。

小试牛刀

  • 监听方法的调用 : rac_signalForSelector
  • 代替KVO:rac_valuesAndChangesForKeyPath
  • 事件监听:rac_signalForControlEvents
  • 代替通知:rac_addObserverForName
  • 监听文本框文本的改变:rac_textSignal
  • 顺序执行:rac_liftSelector:withSignalsFromArray

传统KVO写法:

#import "ViewController.h"
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew context:nil];
    [self.view addObserver:self forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryid> *)change context:(void *)context {
    // 当有个值需要监听时需要大量的if判断
    if ([@"backgroundColor" isEqualToString:keyPath]) {
        NSLog(@"backgroundColor=%@:", change);
    } else if ([@"alpha" isEqualToString:keyPath]) {
        NSLog(@"alpha=%@:", change);
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.view.backgroundColor = [UIColor grayColor];
    self.view.alpha = 0.5;
}

@end

RAC方式(KVO)

#import "ViewController.h"
#import "ReactiveCocoa.h"
#import "NSObject+RACKVOWrapper.h"
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 使用该方法需要手动导入"NSObject+RACKVOWrapper.h"头文件,主头文件中并没有包含该头文件
    [self.view rac_observeKeyPath:@"alpha" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
        NSLog(@"change:%@", change);
    }];

    // 该方法不需要额外导入头文件
    [[self.view rac_valuesAndChangesForKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"x:%@", x);
    }];
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.view.backgroundColor = [UIColor grayColor];
    self.view.alpha = 0.5;
}

@end

两种方式比较:

对于传统方式,监听和处理操作分离开了,如果类中有很多代码,要找到对应的处理方法还是要一秒种时间的,监听和处理分开,感觉代码太分散;如果一个类中有多个监听,在observeValueForKeyPath:方法中会出现大量的判断,使代码看起来很low;

RAC方式的KVO将监听和处理聚合在一块,使代码更加紧凑,也方便查看对应的实现,如果有多个监听,只需要在对应的监听中的代码块中实现即可,没有多余的判断


监听按钮事件(传统方式)

#import "ViewController.h"
@implementation ViewController

- (void)viewDidLoad {
    UIButton *button =[[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];
    [button setTitle:@"Button" forState:UIControlStateNormal];
    button.backgroundColor = [UIColor grayColor];

    // 传统Target-Action方式
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonClick:(UIButton *)button {
    NSLog(@"按钮被点击了:%@", button);
}

@end

RAC 方式:

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController

- (void)viewDidLoad {
     UIButton *button =[[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];
    [button setTitle:@"Button" forState:UIControlStateNormal];
    button.backgroundColor = [UIColor grayColor];

    // 监听和实现放在一起
    [[_button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"按钮被点击了:%@", x);
    }];

    [self.view addSubview:button];   
}
@end

两种方式比较:
传统方式监听和处理分离开了,不便于代码的查找和维护, RAC使代码更加内聚


通知(传统方式)

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowNotification) name:UIKeyboardWillShowNotification object:nil];

- (void)keyboardWillShowNotification{
    NSLog(@"键盘即将显示");
}

RAC方式:

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘即将显示");
    }];

通知和事件一样,都是Target-Action模式,监听和实现分离


监听文本内容改变

// 传统方式
[_textField addTarget:self action:@selector(textFiledContentChange) forControlEvents:UIControlEventEditingChanged];

- (void)textFiledContentChange{
    NSLog(@"文本框内容改变了:");
}
// RAC方式
[[textField rac_textSignal] subscribeNext:^(id x) {
    NSLog(@"文本框内容改变了:%@", x);
}];

实现点击一个按钮调用一个方法的需求:界面上有一个UIView(看做是一个自定义的控件)View上有一个UIButton
ReactiveCocoa基本使用详解_第10张图片

方案1:最基本的实现,直接拖拽UIButton的Action事件即可

#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {

}

- (IBAction)buttonClick:(id)sender {
    NSLog(@"button clicked...");
}
@end

现要求不能直接操作UIView中的子控件,只能操作父容器UIView,而且按钮的点击实现要放到ViewController中,不能放到自定义的UIView对应的自定义的类中(Custom)

方案二:使用代理实现

CustomView :自定义视图类

#import 
// 定义一个代理
@protocol CustomViewDelegate <NSObject>
- (void)buttonDidClick:(UIButton *)button;
@end

@interface CustomView : UIView
@property (weak, nonatomic) id delegate; // 代理属性
@end


#import "CustomView.h"
@implementation CustomView
- (IBAction)buttonClick:(UIButton *)button {
    // 转向代理方法
    [self.delegate buttonDidClick:button];
}
@end
#import "ViewController.h"
#import "CustomView.h"

@interface ViewController ()<CustomViewDelegate>  // 遵守协议
@property (weak, nonatomic) IBOutlet CustomView *customView; // 父视图容器
@end

@implementation ViewController

- (void)viewDidLoad {
    _customView.delegate = self;  // 设置代理
}

- (void)buttonDidClick:(UIButton *)button {
    NSLog(@"button clicked.");
}

@end

当按钮别点击的时候就会调用CustomView中的buttonClick 方法,接着调用代理中的[self.delegate buttonDidClick] 中的buttonDidClick, 而代理是ViewController,所以就调用了[viewController buttonDidClick];

方式3:RAC实现方式

CustomView: 自定义视图

#import 
#import "ReactiveCocoa.h"

@interface CustomView : UIView
@property (strong, nonatomic) RACSubject *subject;
@end

#import "CustomView.h"
@implementation CustomView

- (IBAction)buttonClick:(UIButton *)button {
    // 点击按钮发送信号
    [self.subject sendNext:button];
}

// 懒加载
- (RACSubject *)subject {
    if (_subject == nil) {
        _subject = [RACSubject subject];
    }

    return _subject;
}
@end

ViewController:

#import "ViewController.h"
#import "ReactiveCocoa.h"
#import "CustomView.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet CustomView *customView;
@end

@implementation ViewController
- (void)viewDidLoad {
    // 订阅信号
    [self.customView.subject subscribeNext:^(id x) {
        NSLog(@"button clicked.");
    }];
}
@end

应用启动时首先执行viewDidLoad 方法对信号进行订阅,当点击按钮时,然后发送信号,接着就能执行nextBlock了。
使用RAC方式也达到了代理的作用,使用代理还要创建代理协议并设置代理,没有RAC方式简单

RAC实现方式3:

CustomView:自定义类

#import 
#import "CustomView.h"
@implementation CustomView
- (IBAction)buttonClick:(UIButton *)button {

}
@end
#import "ViewController.h"
#import "ReactiveCocoa.h"
#import "CustomView.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet CustomView *customView;
@end

@implementation ViewController
- (void)viewDidLoad {
    // 监听self.customView这个对象有没有调用过buttonClick:方法,如果执行过就执行下面的代码块
    [[self.customView rac_signalForSelector:@selector(buttonClick:)] subscribeNext:^(id x) {
        NSLog(@"button clicked.");
    }];
}
@end

使用rac_signalForSelector也可以完成这个需求,但是这个方法不能传参,也就是id x 拿不到值



RAC集合

RACTuple:元组,功能类似于NSArray,可以将NSArray包装成元组

RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@1, @2]];
NSLog(@"%@", tuple[0]);

RACSequence:将数组或字典转成成RACSequence,然后再转换成信号RACSignal,订阅信号就是对数组或字典进行遍历

NSArray *array = @[@1, @2, @3];
[array.rac_sequence.signal subscribeNext:^(id x) {
     NSLog(@"x= %@", x);
}];

NSDictionary *dict = @{@"username": @"zhangsan", @"password": @"123"};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
     NSLog(@"key=%@, value=%@", x[0], x[1]); // 通过类似于NSArray中的下标来获取
     RACTupleUnpack(NSString *key, NSString *value) = x;  // 使用宏函数来解析(在Erlang中称为模式匹配)
     NSLog(@"key=%@, value=%@", key, value);
}]; 

多信号执行:当多个信号都发送了数据的时候才执行rac_liftSelector:WithSignalsFromArray

@implementation ViewController
- (void)viewDidLoad {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@"123"];
        return nil;
    }];

    RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@"456"];
        return nil;
    }];

    // 当数组中的所有信号都发送完成才会执行selector,即都执行过sendNext时
    // selector的函数签名:参数的值和信号的顺序保持一致
    // 注意rac_liftSelector:withSignalsFromArray:方法内部会循环数组中的每个信号,调用didSubscribe代码块
    [self rac_liftSelector:@selector(handleReceivedValue:value2:) withSignalsFromArray:@[signal, signal2]];
}

- (void)handleReceivedValue:(NSString *)value value2:(NSString *)value2 {
    NSLog(@"value=%@, value2=%@", value, value2);
}

@end


RAC中常用的宏

  • RAC(obj, property) = signal; // 将信号发送的值赋值给某个对象的某个属性
RAC(_label, text) = _textField.rac_textSignal;

//上面的代码等于下面的代码
[_textField.rac_textSignal subscribeNext:(id x){
    _label.text = x;
}];
  • RACObserve(obj, property); // 返回一个信号,可以去订阅
[RACObserve(self.view, backgroundColor) subscribeNext:^(id x) {
        NSLog(@"x=%@", x);
}];

// 上面代码是下面代码的简写形式
[[self.view rac_valuesAndChangesForKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"x:%@", x);
}];
  • @weakify(self); 将self变成弱指针
  • @strongify(self); 在代码块中将self变成强指针,因为代码块是保存到别的类中,如果不强引用,self可能会销毁,当用到代码块时self已经不再了,这不行,所以要保证self还要存在就变成强指针
    这两个宏用于解决循环引用,在代码块外边使用@weakify(self); 在代码块里面使用@strongify(self);
#import "ModalViewController.h"
#import "ReactiveCocoa.h"

@interface ModalViewController ()
@property (strong, nonatomic) RACSignal *signal;
@end

@implementation ModalViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    @weakify(self);
    _signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        @strongify(self);

        NSLog(@"%@", self);
        return nil;
    }];

}

- (IBAction)popViewController:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)dealloc {
    NSLog(@"dealloc");
}
@end
  • RACTuplePack(@1, @2); // 使用宏将数组包装成元组
RACTuple *tuple = RACTuplePack(@1, @2, @3);
// 上面代码是下面代码的简写
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@1, @2, @3]];

RACMulticastConnection: 多点连接:当一个信号被多次订阅时会执行多次didSubscribe代码块,其中有一些代码只需要执行一次即可,没必要执行多次是可以使用该类,该类只有在连接connect的时候才会执行didSubscribe代码块,即订阅多次只执行一次

  1. 创建信号
  2. 将信号转换成RACMulticastConnection
  3. 订阅RACMulticastConnection的signal
  4. 连接connect
#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController

- (void)viewDidLoad {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        static int i = 0;
        i++;
        NSLog(@"i=%zd", i);

        [subscriber sendNext:@(i)];
        return nil;
    }];

    RACMulticastConnection *multicastConnection = [signal publish];
    [multicastConnection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者1: x=%@", x);
    }];

    [multicastConnection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者2: x=%@", x);
    }];

    [multicastConnection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者3: x=%@", x);
    }];

    [multicastConnection connect];
}

@end

// i=1, 并且只打印了一次
2016-12-15 15:18:29.661 ReactiveCocoa[38910:6392175] i=1
2016-12-15 15:18:29.663 ReactiveCocoa[38910:6392175] 订阅者1: x=1
2016-12-15 15:18:29.664 ReactiveCocoa[38910:6392175] 订阅者2: x=1
2016-12-15 15:18:29.665 ReactiveCocoa[38910:6392175] 订阅者3: x=1

代码解释:
连接connect时执行didSubscribe代码块,执行didSubscribe代码块中的sendNext:时,调用所有的订阅者中的nextBlock代码块

底层代码流程图
ReactiveCocoa基本使用详解_第11张图片

流程解释

第一步:创建信号
创建信号只是简单的将didSubscribe代码块保存到RACSignal(其实是RACDynamicSignal)中

第二步:将信号转换成RACMulticastConnection 对象
底层实现是将步骤一中的创建的信号signal变量保存到RACSignal *sourceSignal属性中
将public方法中新建的RACSubject对象保存到RACSubject *signal属性中;

第三步:订阅信号
订阅信号底层只是创建一个订阅者并将订阅者放到订阅者数组中subscribers

第四步:连接
这步是调用RACDynamicSignal中的订阅方法subscriber,该方法会调用RACSubject中保存的didSubscribe代码块,而didSubscribe代码块会调用sendNext,sendNext会循环订阅者数据,将当前值逐一发送到每个订阅者。

RACCommand:命令,用于处理事件,可以监听事件的执行过程

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController

- (void)viewDidLoad {
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

        NSString *input2 = [NSString stringWithFormat:@"word %@!", input];

        return [RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:input2];

            return nil;
        }];
    }];

    RACSignal *signal = [command execute:@"hello"];
    [signal subscribeNext:^(id x) {
        NSLog(@"x= %@", x);  // x= word hello!
    }];
}

@end

执行过程:
1. 当执行命令execute时会调用初始化命令时的代码块signalBlock,input的值就是execute传的值, 该代码块返回一个信号(即产生一个新的信号signal), 当对返回的信号进行订阅时会执行返回信号对应的didSubscribe代码块,紧接着会执行sendNext:函数,然后执行订阅者的nextBlock代码块

方式二:

- (void)viewDidLoad {
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        NSString *input2 = [NSString stringWithFormat:@"word %@!", input];

        return [RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:input2];

            return nil;
        }];
    }];

    // executionSignals:命令的信号源,信号中的信号,signalOfSignals, x就是命令中返回的信号
    // 这种方式必须在执行前进行订阅
    [command.executionSignals subscribeNext:^(RACSignal* signal) {
        NSLog(@"%@", signal);    // RACDynamicSignal

        [signal subscribeNext:^(id x) {
            NSLog(@"x=%@", x); // x=word hello!
        }];
    }];

    [command execute:@"hello"];
}

executionSignals : 信号源,对信号源进行订阅,如果有新的信号通过订阅会以参数的形式传进来,可以对其订阅获取信号中发送的值

方式三:command.executionSignals.switchToLatest

- (void)viewDidLoad {
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

        NSString *input2 = [NSString stringWithFormat:@"word %@!", input];

        return [RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:input2];

            return nil;
        }];
    }];

    // switchToLatest: 获取最新发送的信号,只能用于信号中的信号,就是SignalBlock返回的信号中的代码块
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"x= %@", x);
    }];

    [command execute:@"hello"];
}

信号中的信号:一个信号发送的值也是一个信号,这种发送的值被称为信号中的信号

- (void)viewDidLoad {
    RACSubject *signalOfSignals = [RACSubject subject];
    [signalOfSignals subscribeNext:^(RACSignal *signal) {
        NSLog(@"signal=%@", signal); // RACSubject

        [signal subscribeNext:^(id x) {
            NSLog(@"x=%@", x);  // x=123
        }];
    }];

    RACSubject *signal = [RACSubject subject];
    [signalOfSignals sendNext:signal];  // 发送的值是一个信号
    [signal sendNext:@"123"];
}
- (void)viewDidLoad {
    RACSubject *signalOfSignals = [RACSubject subject];
    // switchToLatest: 获取signalOfSignals信号 发送的 【最新的】信号signal
    // switchToLatest = signalB
    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=BBB // x=BBBBBBB 打印2次
    }];

    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];
    [signalOfSignals sendNext:signalB];

    [signalB sendNext:@"BBB"];
    [signalA sendNext:@"AAA"];
    [signalB sendNext:@"BBBBBBB"];
}
- (void)viewDidLoad {
    RACSubject *signalOfSignals = [RACSubject subject];

    // switchToLatest = signalC
    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=CCC
    }];

    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];
    RACSubject *signalC = [RACSubject subject];

    // switchToLatest = signalB
    [signalOfSignals sendNext:signalB];
    // switchToLatest = signalC

    [signalB sendNext:@"BBB"];
    [signalA sendNext:@"AAA"];
    [signalB sendNext:@"BBBBBBB"];
    [signalC sendNext:@"CCC"]; 
}

监听命令是否执行过程

- (void)viewDidLoad {
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        NSString *input2 = [NSString stringWithFormat:@"%@ word!", input];

        return [RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:input2];
            [subscriber sendCompleted];  // 如果不显式写发送完成,那么命令一直不结束
            return nil;
        }];
    }];

    // x 是个布尔值,用于判断是否正在执行中
    [command.executing subscribeNext:^(id x) {
        if ([x boolValue] == YES) {
            NSLog(@"执行中ing...");
        } else {
            NSLog(@"没有执行或执行完成!");
        }
    }];

    [[command execute:@"hello"] subscribeNext:^(id x) {
        NSLog(@"x=%@", x);
    }];
}

RAC常见的操作方法:操作方法位于RACSignal类中,所有信号都可以调用

  • bind(绑定)
  • flattenMap(平铺映射):底层调用的是bind
  • map(映射)
  • concat (组合-连接):拿到所有信号的值
  • then :底层调用的concat,
  • merge(合并)
  • zipWith(压缩)
  • combineLatest(聚合)
  • filter(过滤)满足条件才能订阅到值
#import "ViewController.h"
#import "ReactiveCocoa.h"
#import "RACReturnSignal.h"    // 该头文件需要手动导入

@implementation ViewController
- (void)viewDidLoad {
    // 1. 创建源信号
    RACSubject *subject = [RACSubject subject];

    // 2. 创建绑定信号
    RACSignal *bindSignal = [subject bind:^RACStreamBindBlock{
        NSLog(@"信号被订阅时调用该代码块, 一般在这个代码块中不做什么处理");

        return ^RACSignal *(id value, BOOL *stop) {
            // 发送源信号的时候会执行该Block
            // 该Block的作用用于处理源信号发送的值,然后将处理好的数据返回出去
            NSString *newValue= [NSString stringWithFormat:@"%@ ReactiveCocoa!", value];
            return [RACReturnSignal return:newValue];
        };
    }];

    // 3. 订阅绑定信号(可以看成对RACReturnSignal信号的订阅)
    [bindSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x);   // x=hello ReactiveCocoa!
    }];

    // 4. 发送源信号
    [subject sendNext:@"hello"];
}
@end

// bind:一般用于拦截结果,对结果进行再次处理,返回新的结果

bind的调用顺序:

第一步:创建源信号 RACSubject *subject = [RACSubject subject]; 表面上不做任何事
第二步:创建绑定信号 RACSignal *bindSignal = xxx; 执行这行代码表面上也不做任何事
第三步:订阅绑定信号, 会执行bind方法中的外部的代码块bindBlock,不执行该代码块返回的代码块
第四步:发送信号,会执行bind方法返回的代码块(即内部代码块)RACStreamBindBlock,然后执行[RACReturnSignal return:newValue],接着执行订阅信号时对应的代码块nextBlock


flattenMap 示例:

#import "ViewController.h"
#import "ReactiveCocoa.h"
#import "RACReturnSignal.h"

@implementation ViewController
- (void)viewDidLoad {
    RACSubject *subject = [RACSubject subject];

    RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
        NSString *newValue = [NSString stringWithFormat:@"%@ flattenMap!", value];
        return [RACReturnSignal return:newValue];
    }];

    [bindSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=hello flattenMap!
    }];

    [subject sendNext:@"hello"];
}
@end

flattenMap是对bind的包装,flattenMap的中的代码块比bind更加简介

// 执行顺序:sendNext时执行flattenMap中的代码块,执行[RACReturnSignal return:newValue]时调用subscribeNext方法中的代码块nextBlock

// 使用场景:信号中的信号,处理信号中的信号有3种方式

// 第一种
- (void)viewDidLoad {
    RACSubject *signalOfSignals = [RACSubject subject];
    [signalOfSignals subscribeNext:^(RACSignal *x) {
        [x subscribeNext:^(id x) {
            NSLog(@"x=%@", x);  // x=hello RAC
        }];
    }];

    RACSubject *signal = [RACSubject subject];
    [signalOfSignals sendNext:signal];
    [signal sendNext:@"hello RAC"];
}

// 第二种:switchToLatest 
- (void)viewDidLoad { 
    RACSubject *signalOfSignals = [RACSubject subject];

    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=hello RAC
    }];

    RACSubject *signal = [RACSubject subject];
    [signalOfSignals sendNext:signal];
    [signal sendNext:@"hello RAC"];
}

// 第三种:flattenMap
- (void)viewDidLoad {
    RACSubject *signalOfSignals = [RACSubject subject];

    [[signalOfSignals flattenMap:^RACStream *(id value) {
        return value;           // value = signal
    }] subscribeNext:^(id x) {
        NSLog(@"x=%@", x);     // x=hello RAC
    }];

    RACSubject *signal = [RACSubject subject];
    [signalOfSignals sendNext:signal];

    [signal sendNext:@"hello RAC"];
}

map:

#import "ViewController.h"
#import "ReactiveCocoa.h"

@implementation ViewController

- (void)viewDidLoad {   
    RACSubject *subject = [RACSubject subject];

    RACSignal *bindSignal = [subject map:^id(id value) {
        NSString *newValue = [NSString stringWithFormat:@"%@ flattenMap!", value];

        return newValue;
    }];

    [bindSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=hello flattenMap!
    }];

    [subject sendNext:@"hello"];
}
@end

执行流程:sendNext时执行map中的代码块,当map中的代码块返回值时,执行subscribeNext的nextBlock代码块;

在bind、flattenMap、map中,bind方法的返回值最复杂,flattenMap对其bind进行封装,返回值相对简单写,而map直接返回id,是最简单的;flattenMap用于信号中的信号,map用于一般的拦截


concat(连接):信号B连接到信号A后面,只有信号A执行完才会执行信号B,两个信号都执行同一个nextBlock

- (void)viewDidLoad {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        NSLog(@"signalA...");
        [subscriber sendNext:@"123"];
        [subscriber sendCompleted];
        return nil;
    }];

    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        NSLog(@"signalB...");
        [subscriber sendNext:@"456"];
        return nil;
    }];

    // 将信号B连接在信号A后面,只要信号A发送完成才会执行信号B
    RACSignal *contactSignal = [signalA concat:signalB];
    [contactSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=123 // x=456 执行两次
    }];
}

执行顺序:contactSignal 连接信号不需要调用sendNext,只要订阅就会执行信号中的didSubscribe代码块,按照顺序,首先先执行信号A中的代码块,当执行到sendNext时再执行组合信号中的nextBlock,接着再执行信号A中的发送完成sendCompleted,接着执行信号B中的代码块,因代码块中发送了数据,所以接着执行连接信号的nextBlock代码块。

注意:只有前面信号执行发送完成sendCompleted的时候才会执行后面的代码块,如果前面信号没有调用sendCompleted方法,那么后面的信号将不会执行。如果前面代码块没有写sendCompleted方法,前面代码块的信号值会发送到组合信号中的nextBlock中的,但是后面的信号就没机会执行了

concat:可以将前面的信号理解为 “皇上”,后面的信号理解为“皇太子”,只有“皇上”驾崩了(sendCompleted) “皇太子”才能登基。


then:后面的信号会被订阅到,前面的信号将被忽略

- (void)viewDidLoad {

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        NSLog(@"signalA...");
        [subscriber sendNext:@"123"];
        [subscriber sendCompleted];
        return nil;
    }];

    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        NSLog(@"signalB...");
        [subscriber sendNext:@"456"];
        return nil;
    }];

    RACSignal *thenSignal =[signalA then:^RACSignal *{
        return signalB;
    }];

    [thenSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x); // x=456
    }];
}

使用then连接的信号会忽略掉前面信号发送的值,只接收后面一个信号发送的值,和concat一样,同样前面信号需要调用发送完完成sendCompleted,如果不调用后面信号将得不到执行


merge(合并):将两个信号合并成一个信号,无论哪一个发出信号都会执行合并信号的nextBlock

- (void)viewDidLoad {
    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];

    RACSignal *mergeSignal = [signalA merge:signalB];
    [mergeSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=123 // x=456 打印2次
    }];

    [signalA sendNext:@"123"];
    [signalB sendNext:@"456"];
}

zipWith(压缩):将两个信号压缩成一个信号,并且将两个信号发送的value封装成元组传到nextBlock中,压缩代码块只会执行一次

- (void)viewDidLoad {
    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];

    RACSignal *zipSignal = [signalA zipWith:signalB];
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x= (123, 456)

    }];

    [signalA sendNext:@"123"];
    [signalB sendNext:@"456"];
}

zipWith: 将2个信号压缩在一起,只有两个信号都发送值了,才会执行压缩信号对应的nextBlock,只发送一个信号是不会执行的,x的值是一个元组类型RACTuple,同时包装这两个信号发送的值

可以将zipWith理解为“夫妻关系”,也即使说只有两个人同意(sendNext)才好使


combineLatest(聚合):
小示例:登录时只有用户名和密码都输入信息时,登录按钮才可点击

#import "ViewController.h"
#import "ReactiveCocoa.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
@property (weak, nonatomic) IBOutlet UITextField *pawwordTextField;
@property (weak, nonatomic) IBOutlet UIButton *loginButton;
@end

@implementation ViewController
- (void)viewDidLoad {
    NSArray *signals = @[_usernameTextField.rac_textSignal, _pawwordTextField.rac_textSignal];

    RACSignal *combineSignal = [RACSignal combineLatest:signals reduce:^id(NSString *username, NSString *password){
        return @(username.length && password.length);
    }];

    [combineSignal subscribeNext:^(id x) {
        self.loginButton.enabled = [x boolValue];
    }];
}
@end

ReactiveCocoa基本使用详解_第12张图片

combineLatest: 内聚,将多个信号聚合成一个信号,当每个信号sendNext的时候都会调用聚合信号的nextBlock,(rac_textSignal信号内部会自动调用sendNext)

filter:只有满足条件的信号才会被发送,返回一个布尔值

- (void)viewDidLoad {
    [[_textField.rac_textSignal filter:^BOOL(id value) {
        return [value length] >= 6;
    }] subscribeNext:^(id x) {
        NSLog(@"x=%@", x);
    }];
}

ignore(忽略):用于忽略某个Value

- (void)viewDidLoad {
    RACSubject *subjct = [RACSubject subject];
    RACSignal *ignoreSignal = [subjct ignore:@"123"];
    [ignoreSignal subscribeNext:^(id x) {
        NSLog(@"x= %@", x);  // x=456
    }];

    [subjct sendNext:@"123"];
    [subjct sendNext:@"456"];
}

ignore:用于忽略掉某个值,忽略掉的值将不会触发订阅中的nextBlock


take(获取)取前面N次信号,限制nextBlock执行的次数,示例中只执行2次,第三次不再执行

- (void)viewDidLoad {
    RACSubject *subjct = [RACSubject subject];
    RACSignal *takeSignal = [subjct take:2];
    [takeSignal subscribeNext:^(id x) {
        NSLog(@"x= %@", x);  // x=123 x=456
    }];

    [subjct sendNext:@"123"];
    [subjct sendNext:@"456"];
    [subjct sendNext:@"789"];
}

takeLast(获取最后):取最后N次信号,只有发送完成才会执行nextBlock,要不然不知道什么时候结束,也不知道从那里算最后几个, 注意,取最后N次,但是是正序执行的

- (void)viewDidLoad {
    RACSubject *subjct = [RACSubject subject];
    RACSignal *takeLastSignal = [subjct takeLast:2];
    [takeLastSignal subscribeNext:^(id x) {
        NSLog(@"x= %@", x);  // x=456 x=789
    }];

    [subjct sendNext:@"123"];
    [subjct sendNext:@"456"];
    [subjct sendNext:@"789"];
    [subjct sendCompleted]; // 发送完成
}

takeUntil: 当向信号触发器(signalTrigger)发送完成sendCompleted或者发送值sendNext时,后面的信号将不再接收,也就是nextBlock以后将不再执行,发送错误sendError,后面订阅者仍能接收到信号

- (void)viewDidLoad {
    RACSubject *subjct = [RACSubject subject];
    RACSubject *signalTrigger = [RACSubject subject];

    [[subjct takeUntil:signalTrigger] subscribeNext:^(id x) {
        NSLog(@"x=%@", x);  // x=123
    }];

    [subjct sendNext:@"123"];
    [signalTrigger sendCompleted];
//    [signalTrigger sendNext:@"signl123"];
    [signalTrigger sendError:nil];
    [subjct sendNext:@"456"];
    [subjct sendNext:@"789"];
}

distinctUntilChanged(唯一直到改变为止) 当下一发送的值和上一次发送的值一样,下一次将不会被订阅,也就是下一次不执行nextBlock

- (void)viewDidLoad {
    RACSubject *subjct = [RACSubject subject];

    [[subjct distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"x=%@", x); // x=123 // x=456
    }];

    [subjct sendNext:@"123"];
    [subjct sendNext:@"456"];
    [subjct sendNext:@"456"]; // 下次和上次一样,下次将被忽略
}

skip:跳过前面N次信号,前面N次不会执行nextBlock

- (void)viewDidLoad {
    RACSubject *subjct = [RACSubject subject];

    [[subjct skip:2] subscribeNext:^(id x) {
        NSLog(@"x=%@", x); // x=789
    }];

    [subjct sendNext:@"123"];
    [subjct sendNext:@"456"];
    [subjct sendNext:@"789"];
}

下面是摘自其他博客中的几段话:

有一堆的函数能够创建(create)任何流,也能将任何流进行组合(combine)和过滤(filter)。 这正是“函数式”的魔力所在。一个流能作为另一个流的输入(input),甚至多个流也可以作为其它流的输入。你能合并(merge)两个流。你还能通过过滤(filter)一个流得到那些你感兴趣的事件。你能将一个流中的数据映射(map)到一个新的流中。

 一个流就是一个将要发生的以时间为序的事件序列。它能发射出三种不同的东西:一个数据值(data value)(某种类型的),一个错误(error)或者一个“完成(completed)”的信号。比如说,当前按钮所在的窗口或视图关闭时,“单击”事件流也就“完成”了。

我们只能异步地捕获这些发出的事件:定义一个针对数据值的函数,在发出一个值时,该函数就会异步地执行;针对发出错误时的函数;还有针对发出‘完成’时的函数。有时你可以省略这最后两个函数,只专注于针对数据值的函数。“监听”流的行为叫做订阅。我们定义的这些函数就是观察者。这个流就是被观察的主体(subject)(或“可观察的(observable)”)。这正是观察者设计模式。

ReactiveCocoa基本使用详解_第13张图片


你可能感兴趣的:(iOS)