美文网首页
IOS框架:Reactive Cocoa

IOS框架:Reactive Cocoa

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-27 10:30 被阅读0次

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、ReactiveCocoa的使用
    • 1、RACSignal
    • 2、RACSubject
    • 3、RACCommand
    • 4、RACMulticastConnection
    • 5、RACTuple和RACSequence
    • 6、事件监听
    • 7、宏
    • 8、操作方法之映射
    • 9、操作方法之组合
    • 10、操作方法之过滤
    • 11、操作方法之秩序
    • 12、操作方法之线程
    • 13、操作方法之时间
    • 14、操作方法之重复
    • 15、RAC双向绑定
  • 二、ReactiveCocoa的源码解析
    • 1、RACSignal
    • 2、RACSubject
    • 3、RACReplaySubject
    • 4、RACMulticastConnection
    • 5、RACCommand
  • Demo
  • 参考文献

十三、函数响应式编程

  • ReactiveCocoa: 函数响应式编程,搭配MVVM 使用
  • ReactiveObjC:RAC提供Signals来捕获当前值和将来值
  • RxSwiftSwift 函数响应式编程框架
  • PromiseKit:提供了很多实用的异步函数
  • HansonSwift中的轻量级观察和绑定

一、ReactiveCocoa的使用

ReactiveCocoa中的核心类是RACSignal,其子类包括RACDynamicSignalRACReturnSignalRACEmptySignalRACErrorSignal。用到的函数有:map take skip ignore filter

1、RACSignal

这是ReactiveCocoa的核心类,用来表示数据传递,只要有数据变化,信号内部收到数据后,就会马上发出数据。

❶ 使用方法
// 创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe

// 订阅信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

// 发送信号
- (void)sendNext:(id)value

// 这是信号的订阅者,本质是一个协议,遵守这个协议的类对象,并且实现协议方法就可以成为一个订阅者
RACSubscriber

// 这是用于取消订阅或者清理资源的类,当信号发送完成或者发送错误的时候会自动触发调用
RACDisposable
❷ 实现原理
  1. 创建信号,首先把didSubscribe保存到信号中,还不会触发。
  2. 当信号被订阅,也就是调用signalsubscribeNext:nextBlock
  3. subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到订阅者subscriber中。
  4. subscribeNext内部会调用之前创建的信号siganldidSubscribe
  5. siganldidSubscribe中调用发送信号[subscriber sendNext:@1];
  6. 发送信号sendNext底层其实就是执行订阅者subscribernextBlock
❸ Demo演示
- (void)RACSignalDemo
{
    // 1.创建信号
    RACSignal *single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // block调用时刻:每当有订阅者订阅信号,就会调用block
        
        NSLog(@"想");
        
        // 2.发送信号
        [subscriber sendNext:@"发送了信号"];
        
        NSLog(@"你");
        
        // 如果不再发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号
        [subscriber sendCompleted];
        
        // 执行完信号后进行的清理工作,如果不需要就返回 nil
        return [RACDisposable disposableWithBlock:^{
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号
            
            // 执行完Block后,当前信号就不再被订阅了
            NSLog(@"豆腐");
        }];
    }];
    
    NSLog(@"我");
    
    // 3.订阅信号,才会激活信号
    [single subscribeNext:^(id x) {
        // block调用时刻:每当有信号发出数据,就会调用block
        NSLog(@"吃");
        NSLog(@"信号的值:%@",x);
    }];
}

输出结果为:

2020-09-16 10:17:41.359920+0800 框架Demo[54921:9299537] 我
2020-09-16 10:17:41.361400+0800 框架Demo[54921:9299537] 想
2020-09-16 10:17:41.361493+0800 框架Demo[54921:9299537] 吃
2020-09-16 10:17:41.361559+0800 框架Demo[54921:9299537] 信号的值:发送了信号
2020-09-16 10:17:41.361627+0800 框架Demo[54921:9299537] 你
2020-09-16 10:17:41.361712+0800 框架Demo[54921:9299537] 豆腐

没错,输出:我 想 吃 你 豆腐。哈哈,我很喜欢这个Demo,因为信号的运行流程描述得非常形象。

❹ 流程解释
  1. createSignal方法是创建信号,创建好的信号,没有被订阅前,只是冷信号,此时是不会走createSignal后面的block的。程序往下,就走到“NSLog(@"我")”
  2. 然后走到subscribeNext,这一步就是订阅信号,订阅号信号后,信号single就变成了热信号。
  3. 既然变成热信号,就开始走createSignal后面的block,所以就打印出了“NSLog(@"想")”
  4. 下面是sendNext,即发送信号,发送了信号,订阅者就会收到信号,发送的内容可以从订阅信号subscribeNext后面的block中获取到,程序就走到subscribeNext后面的block中,所以就打印了“NSLog(@"吃")”
  5. 当订阅信号的subscribeNext后面的block走完以后,程序又回到createSignal后面的block中,继续未完成的代码,所以就打印“NSLog(@"你")”,继续往下就是[subscriber sendCompleted],这句代码的意思是,发送完成了,订阅自动移除,没有了订阅者了,信号又变成了冷信号。
  6. 接下来就是return,返回一个RACDisposable对象,这个的作用就是,可以用来手动移除订阅。RACDisposable对象,创建完成,就走进创建方法的block中,也就是打印NSLog(@"豆腐")
❺ RACDisposable 手动删除订阅的使用
- (void)RACDisposableDemo
{
    // 1.创建信号
    RACSignal * single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"想");
        // 2.发送信号
        [subscriber sendNext:@"发送了一个信号"];
        NSLog(@"你");
        
        //RACDisposable 手动移除订阅者
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"豆腐");
        }];
    }];
    
    // 3.订阅信号
    NSLog(@"我");
    RACDisposable * disposable = [single subscribeNext:^(id x) {
        NSLog(@"吃");
        NSLog(@"信号的值:%@",x);
    }];
    
    // 4.手动移除订阅
    [disposable dispose];
}

输出结果为:

2020-09-16 10:28:15.616011+0800 框架Demo[55046:9308956] 我
2020-09-16 10:28:15.617600+0800 框架Demo[55046:9308956] 想
2020-09-16 10:28:15.617697+0800 框架Demo[55046:9308956] 吃
2020-09-16 10:28:15.617768+0800 框架Demo[55046:9308956] 信号的值:发送了一个信号
2020-09-16 10:28:15.617837+0800 框架Demo[55046:9308956] 你
2020-09-16 10:28:15.617935+0800 框架Demo[55046:9308956] 豆腐

两份代码不同之处是,删去了自动移除订阅[subscriber sendCompleted],添加了手动删除订阅[disposable dispose]

不过手动删除用的少。那既然用得少,我们还是用自动删除吧,优化下,返回nil就可以了。

- (RACSignal *)autoDeleteSubscription
{
    // 1.创建信号
    RACSignal *single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"想");
        // 2.发送信号
        [subscriber sendNext:@"发送了信号"];
        NSLog(@"你");
        
        // 4.发送完成,订阅自动移除
        [subscriber sendCompleted];
        
        // RACDisposable 手动移除订阅者
        return nil;
    }];
    
    NSLog(@"我");
    // 3.订阅信号
    [single subscribeNext:^(id x) {
        NSLog(@"吃");
        NSLog(@"信号的值:%@",x);
    }];
    
    return single;
}

输出结果为:

2020-09-16 10:33:16.676630+0800 框架Demo[55133:9313860] 我
2020-09-16 10:33:16.678199+0800 框架Demo[55133:9313860] 想
2020-09-16 10:33:16.678296+0800 框架Demo[55133:9313860] 吃
2020-09-16 10:33:16.678368+0800 框架Demo[55133:9313860] 信号的值:发送了信号
2020-09-16 10:33:16.678436+0800 框架Demo[55133:9313860] 你

2、RACSubject

这是信号的提供者,被称为信号的信号,即既可以自己充当信号,也能够发送信号,常用来代替代理。

❶ 使用方法
// 创建信号
[RACSubject subject]

// 订阅信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

// 发送信号
sendNext:(id)value
❷ Demo演示
// RACSubject的使用
- (void)RACSubjectDemo
{
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 2.订阅信号
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用
        NSLog(@"第一个订阅者:%@",x);
    }];
    
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用
        NSLog(@"第二个订阅者:%@",x);
    }];
    
    // 3.发送信号
    [subject sendNext:@"谢佳培"];
}

输出结果为:

2020-09-16 10:42:03.537529+0800 框架Demo[55263:9321680] 第一个订阅者:谢佳培
2020-09-16 10:42:03.537649+0800 框架Demo[55263:9321680] 第二个订阅者:谢佳培
❸ 实现原理

RACSubject底层实现和RACSignal不一样。

  1. 调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
  2. 调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个接一个调用订阅者的nextBlock
❹ 替换代理
需求
  • 给第一个控制器添加一个按钮,用来跳转到第二个控制器界面。
  • 在第二个控制器上也添加个按钮,点击按钮,通知第一个控制器。
第一个控制器
@interface FirstViewController : UIViewController

@end

@implementation FirstViewController

- (void)viewDidLoad
{
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(120, 300, 200, 100)];
    button.backgroundColor = [UIColor blackColor];
    [button setTitle:@"跳转到第二个控制器" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(butttonClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

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

/** 添加一个RACSubject代替代理 */
@property (nonatomic, strong) RACSubject *delegateSignal;

@end

@implementation SecondViewController

- (void)viewDidLoad
{
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(120, 500, 200, 100)];
    button.backgroundColor = [UIColor blackColor];
    [button setTitle:@"通知第一个控制器" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(notice) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

// 监听第二个控制器按钮是否被点击,如果被点击了则通知第一个控制器
- (void)notice
{
    // 判断代理信号是否有值
    if (self.delegateSignal)
    {
        // 有值,才需要通知
        [self.delegateSignal sendNext:nil];
    }
}

@end

输出结果为:

2020-09-16 11:07:36.287531+0800 框架Demo[55772:9350854] 点击了通知按钮
❺ RACRelaySubject
用途

继承自RACSubject,特点是 订阅信号 和 发送信号 没有先后顺序,而RACSubject就必须先订阅后发送。常用来解决创建信号的block会带来副作用的场合,以及限制缓存值的数量。

使用
- (void)RACRelaySubjectDemo
{
    // 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);
    }];
}

输出结果为:

2020-09-16 11:25:27.733886+0800 框架Demo[55997:9363518] 第一个订阅者接收到的数据为:1
2020-09-16 11:25:27.734014+0800 框架Demo[55997:9363518] 第一个订阅者接收到的数据为:2
2020-09-16 11:25:27.734763+0800 框架Demo[55997:9363518] 第二个订阅者接收到的数据为:1
2020-09-16 11:25:27.734913+0800 框架Demo[55997:9363518] 第二个订阅者接收到的数据为:2
原理

RACReplaySubject底层实现和RACSubject不一样。

  1. 调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个接一个调用订阅者的nextBlock
  2. 调用subscribeNext订阅信号,遍历保存的所有值,一个接一个调用订阅者的nextBlock

3、RACCommand

用于处理各种事件的类,可以把事件如何处理和数据如何传递,封装到这个类中,方便监控事件的执行过程,常用来处理网络请求场景

❶ 使用方法
// 创建signalBlock,并返回信号
initWithSignalBlock:(RACSignal * (^)(id input))signalBlock

// 执行命令
- (RACSignal *)execute:(id)input
❷ Demo演示
@property (nonatomic, strong) RACCommand *command;

- (void)RACCommandDemo
{
    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        NSLog(@"执行命令");
        
        // 创建空信号。必须返回信号
        // return [RACSignal empty];
        
        // 2.创建信号,用来传递数据
        return [RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:@"请求接口返回的数据"];
            
            // 数据传递完成,调用sendCompleted,这时命令才执行完毕
            [subscriber sendCompleted];
            
            return nil;
        }];
    }];
    
    // 强引用命令,不要被销毁,否则接收不到数据
    self.command = command;
    
    // 3.订阅RACCommand中的信号
    [command.executionSignals subscribeNext:^(id x) {
        [x subscribeNext:^(id x) {
            NSLog(@"订阅RACCommand中的信号:%@",x);
        }];
    }];
    
    // RAC高级用法
    // switchToLatest:获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"获取signal of signals发出的最新信号:%@",x);
    }];
    
    // 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号
    [[command.executing skip:1] subscribeNext:^(id x) {
    
        if ([x boolValue] == YES)// 正在执行
        {
            NSLog(@"正在执行");
        }
        else// 执行完成
        {
            NSLog(@"执行完成");
        }
    }];
    
    // 5.执行命令
    [self.command execute:@1];
}

输出结果为:

2020-09-16 11:38:51.237267+0800 框架Demo[56159:9372910] 执行命令
2020-09-16 11:38:51.240593+0800 框架Demo[56159:9372910] 正在执行
2020-09-16 11:38:51.240839+0800 框架Demo[56159:9372910] 订阅RACCommand中的信号:请求接口返回的数据
2020-09-16 11:38:51.240923+0800 框架Demo[56159:9372910] 获取signal of signals发出的最新信号:请求接口返回的数据
2020-09-16 11:38:51.241238+0800 框架Demo[56159:9372910] 执行完成
❸ 注意事项
  • signalBlock必须要返回一个信号,不能传nil
  • 如果不想要传递信号,直接创建空的信号[RACSignal empty];
  • RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
  • RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
❹ 设计思想

signalBlock为什么要返回一个信号,这个信号有什么用?
在实际开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。

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


4、RACMulticastConnection

用于当一个信号被多次订阅时,为了保证创建信号时避免多次调用创建信号中的block造成副作用,可以使用这个类处理,即RACMulticastConnection用来解决重复请求问题。

❶ 使用方法
// 创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe

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

// 订阅信号
[connect.signal subscribeNext:nextBlock]

// 连接发送信号
[connect connect]
❷ Demo演示
- (void)RACMulticastConnectionDemo
{
    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id 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];
}

输出结果为:

2020-09-16 13:47:33.666865+0800 框架Demo[57362:9432620] 发送请求
2020-09-16 13:47:33.667009+0800 框架Demo[57362:9432620] 订阅者一的信号
2020-09-16 13:47:33.667090+0800 框架Demo[57362:9432620] 订阅者二的信号
❸ 实现原理
  1. 创建connectconnect.sourceSignal ->RACSignal(原始信号), connect.signal-> RACSubject
  2. 订阅connect.signal,会调用RACSubjectsubscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block
  3. [connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
  4. 订阅原始信号,就会调用原始信号中的didSubscribe
  5. didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubjectsendNext
  6. RACSubjectsendNext会遍历RACSubject所有订阅者发送信号。
  7. 因为刚刚第二步都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock

5、RACTuple和RACSequence

RACTuple:元组类,类似NSArray,在解构对象中经常使用。
RACSequence:集合类,使用它来快速遍历数组和字典。

- (void)RACTupleAndRACSequenceDemo
{
    // 1.遍历数组
    NSArray *numbers = @[@1,@2,@3,@4];
    
    // numbers.rac_sequence:把数组转换成集合RACSequence
    // numbers.rac_sequence.signal:把集合RACSequence转换RACSignal信号类
    // 订阅信号,激活信号,会自动把集合中的所有值,遍历出来
    [numbers.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"遍历数组:%@",x);
    }];
    
    // 2.遍历字典。遍历出来的键值对会包装成RACTuple(元组对象)
    NSDictionary *dict = @{@"name":@"谢佳培",@"birth":@1997};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
    
        // 解包元组,会把元组的值按顺序给参数里面的变量赋值
        RACTupleUnpack(NSString *key,NSString *value) = x;
        NSLog(@"key:%@,value:%@",key,value);
        
        // 相当于以下写法
        NSString *keyStr = x[0];
        NSString *valueStr = x[1];
        NSLog(@"keyStr:%@,valueStr:%@",keyStr,valueStr);
    }];
}

输出结果为:

2020-09-16 14:05:35.894392+0800 框架Demo[57599:9447492] 遍历数组:1
2020-09-16 14:05:35.894627+0800 框架Demo[57599:9447492] 遍历数组:2
2020-09-16 14:05:35.894746+0800 框架Demo[57599:9447492] 遍历数组:3
2020-09-16 14:05:35.894845+0800 框架Demo[57599:9447492] 遍历数组:4
2020-09-16 14:05:35.895003+0800 框架Demo[57599:9447492] 遍历数组:5
2020-09-16 14:05:35.895307+0800 框架Demo[57599:9447491] key:name,value:谢佳培
2020-09-16 14:05:35.895384+0800 框架Demo[57599:9447491] keyStr:name,valueStr:谢佳培
2020-09-16 14:05:35.895513+0800 框架Demo[57599:9447491] key:birth,value:1997
2020-09-16 14:05:35.895582+0800 框架Demo[57599:9447491] keyStr:birth,valueStr:1997

6、事件监听

输出结果为:

2020-09-16 16:37:31.678816+0800 框架Demo[59664:9563723] 用于更新UI的数据:firstData_发送请求1,secondData_发送请求2
2020-09-16 16:53:20.627209+0800 框架Demo[59999:9584933] 蒙奇 D 路飞
2020-09-16 16:37:52.625954+0800 框架Demo[59664:9563723] 文字改变了1
2020-09-16 16:37:52.701440+0800 框架Demo[59664:9563723] 文字改变了12
2020-09-16 16:37:52.870946+0800 框架Demo[59664:9563723] 文字改变了123
2020-09-16 16:37:47.692000+0800 框架Demo[59664:9563723] 按钮被点击了
2020-09-16 16:39:37.193890+0800 框架Demo[59664:9563723] 键盘弹出
2020-09-16 16:54:31.237834+0800 框架Demo[59999:9584933] 新值为:<RACTwoTuple: 0x600000830440> (
    "NSPoint: {0, -0.66666666666666674}",
        {
        kind = 1;
        new = "NSPoint: {0, -0.66666666666666674}";
    }
)
❶ 代替代理:rac_signalForSelector

以前需要遵守代理协议,赋值delegate,实现代理方法等,如果用rac_signalForSelector则都不需要。

[[self rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext:^(id x) {
    NSLog(@"蒙奇 D 路飞");
}];
❷ 代替KVO:rac_valuesAndChangesForKeyPath
UIScrollView *scrolView = [[UIScrollView alloc] initWithFrame:CGRectMake(200, 300, 200, 200)];
scrolView.contentSize = CGSizeMake(200, 400);
scrolView.backgroundColor = [UIColor greenColor];
[self.view addSubview:scrolView];

[[scrolView rac_valuesAndChangesForKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew observer:self] subscribeNext:^(id x) {
    NSLog(@"新值为:%@",x);
}];
❸ 监听事件:rac_signalForControlEvents
[[self.eventButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    NSLog(@"按钮被点击了");
}];
❹ 代替通知:rac_addObserverForName
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
    NSLog(@"键盘弹出");
}];
❺ 监听文本框文字改变:rac_textSignal

使用RACSignal创建信号,其实文本框中文字改变也是信号,按钮点击也是信号,RACUITxtFieldUIButton创建categary,并做好了封装,直接就可以调用它们的信号。因为textField的信号肯定是NSString类型的,所以可以将id x修改为NSString* x。当你在文本框输入时,系统就会捕捉输入内容将其在控制台打印。

[self.eventTextField.rac_textSignal subscribeNext:^(id x) {
    NSLog(@"文字改变了%@",x);
}];
❻ 同步信号:rac_liftSelector:withSignalsFromArray:Signals
RACSignal *firstRequest = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"发送请求1"];
    return nil;
}];

RACSignal *secondRequest = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"发送请求2"];
    return nil;
}];

// 同步信号。每个参数对应信号发出的数据
[self rac_liftSelector:@selector(updateUIWithFirstRequestData:secondRequestData:) withSignalsFromArray:@[firstRequest,secondRequest]];

- (void)updateUIWithFirstRequestData:(id)firstData secondRequestData:(id)secondData
{
    NSLog(@"用于更新UI的数据:firstData_%@,secondData_%@",firstData,secondData);
}

7、宏

❶ 宏RAC

提供了一个宏"RAC(对象,属性)"来简化代码并增强可读性。RAC宏有两个参数,一个是需要设置的对象,一个是设置的属性。

// 只要文本框文字改变,就会修改label的文字
RAC(self.macroLabel,text) = self.macroTextField.rac_textSignal;

// 文本框文字长度大于4为红色背景,小于4为蓝色背景,textView同理
RAC(self.macroTextField ,backgroundColor) = [self.macroTextField.rac_textSignal map:^id(NSString* value) {
    return value.length > 4 ? [UIColor redColor]:[UIColor blueColor];
}];
RAC
❷ 宏RACObserve

RACObserve用来监听某个对象的某个属性,返回的是信号。

// view.center的值
[RACObserve(self.view, center) subscribeNext:^(id x) {
    NSLog(@"view.center的值为:%@",x);
}];

输出结果为:

2020-09-16 17:17:15.679022+0800 框架Demo[60348:9606184] view.center的值为:NSPoint: {207, 448}
❸ @weakify(Obj) 和 strongify(Obj):处理闭包强引用

RAC的所有方法中,大部分是block,所以无法避免在使用过程中导致循环引用,以前的解决办法是这样的:

- (void)weakifyDemo
{
    __weak typeof(self) weakSelf;
    [[[self.weakifyButton rac_signalForControlEvents:(UIControlEventTouchUpInside)]
      doNext:^(id x) {
        
        weakSelf.weakifyLabel.textColor = [UIColor redColor];
    }] subscribeNext:^(UIControl *x) {
        weakSelf.weakifyLabel.text = @"1314";
    }];
}

如果每个block都写的话,会很费劲,因为block太多了,还好RAC提供了两个宏,@weakify@strongify

- (void)weakifyDemo
{
    @weakify(self);
    [[[self.weakifyButton rac_signalForControlEvents:(UIControlEventTouchUpInside)]
      doNext:^(id x) {
        @strongify(self);
        self.weakifyLabel.textColor = [UIColor redColor];
    }] subscribeNext:^(UIControl *x) {
        self.weakifyLabel.text = @"1314";
    }];
}

@weakify宏让你创建一个弱引用的影子对象(如果你需要多个弱引用,你可以传入多个变量),@strongify让你创建一个对之前传入@weakify对象的强引用。这样就解决了循环引用的问题。

❹ RACTuple 和 RACTupleUnpack

元组的构造与解构。

// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"谢佳培",@1997);

// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *name,NSNumber *birth) = tuple;
NSLog(@"name:%@,birth:%@",name,birth);

输出结果为:

2020-09-16 17:24:45.740230+0800 框架Demo[60442:9612778] name:谢佳培,birth:1997

8、操作方法之映射

❶ flattenMap
作用

可以把源信号的内容映射成一个新的信号,信号可以是任意类型。

使用方法
  1. 传入一个block [返回值是RACSignal,参数是value]。
  2. 参数value就是源信号的内容,在block内拿到源信号的内容做处理。
  3. 包装成RACReturnSignal信号作为返回值。
底层实现
  1. flattenMap内部调用bind方法,flattenMapblock的返回值会作为bindbindBlock的返回值。
  2. 当订阅绑定信号,就会生成bindBlock
  3. 当源信号发送内容,就会调用bindBlock(value, *stop)
  4. 调用bindBlock,内部就会调用flattenMapblockflattenMapblock作用:就是把处理好的数据包装成信号。
  5. 返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
  6. 订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
Demo演示
- (void)flattenMapDemo
{
    // 创建源信号
    RACSubject *subject = [RACSubject subject];
    
    // 绑定信号
    RACSignal *bindSignal = [subject flattenMap:^RACSignal *(id value) {
        // block:只要源信号发送内容就会调用
        
        // value:就是源信号发送的内容
        NSString *str = [NSString stringWithFormat:@"%@",value];
        NSLog(@"源信号发送的内容为:%@",str);

        return [RACReturnSignal return:value];
        
    }];
    
    // 订阅绑定信号。flattenMap中返回的是什么信号,订阅的就是什么信号
    [bindSignal subscribeNext:^(id x) {
        NSLog(@"订阅的绑定信号收到的内容为:%@",x);
    }];
    
    // 源信号发送数据
    [subject sendNext:@"哪吒和孙悟空"];
}

输出结果为:

2020-09-16 17:51:20.331132+0800 框架Demo[60815:9633081] 源信号发送的内容为:哪吒和孙悟空
2020-09-16 17:51:20.331289+0800 框架Demo[60815:9633081] 订阅的绑定信号收到的内容为:哪吒和孙悟空
❷ Map
作用

把源信号的值映射成一个新的值,返回一个对象,就是将一种信号转换成你想要的另一种信号。

使用方法
  1. 传入一个block [返回值是RACSignal,参数是value]。
  2. value就是源信号的内容,直接拿到源信号的内容做处理
  3. 把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
底层实现
  1. Map底层其实是调用flatternMapMapblock中的返回的值会作为flatternMapblock中的值。
  2. 当订阅绑定信号,就会生成bindBlock
  3. 当源信号发送内容,就会调用bindBlock(value, *stop)
  4. 调用bindBlock,内部就会调用flattenMapblock
  5. flattenMapblock内部会调用Map中的block,把Map中的block返回的内容包装成返回的信号。
  6. 返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
  7. 订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送。
Demo演示
- (void)mapDemo
{
    RACSubject *subject = [RACSubject subject];
    RACSignal *bindSignal = [subject map:^id(id value) {
        // 返回的类型,就是你需要映射的值
        return [NSString stringWithFormat:@"%@",value];
    }];
    [bindSignal subscribeNext:^(id x) {
        NSLog(@"神话英雄:%@",x);
    }];
    
    [subject sendNext:@"哪吒"];
    [subject sendNext:@"悟空"];
}

输出结果为:

2020-09-16 18:06:47.150435+0800 框架Demo[61008:9645534] 神话英雄:哪吒
2020-09-16 18:06:47.150610+0800 框架Demo[61008:9645534] 神话英雄:悟空

如果想当文本框中输入的文字长度大于4的时候,改变文本框的背景色,一种方法是把过滤器的条件设置为4,然后在subscribeNextblock中直接给textField.backgroundColor赋值。不过RAC有转换信号的方法---map,如下

- (void)mapColorDemo
{
    [[[self.mapTextField.rac_textSignal filter:^BOOL(NSString* value) {
        return value.length > 3 ? YES:NO;
    }] map:^id(NSString* value) {
        return value.length > 4 ? [UIColor redColor] : [UIColor blackColor];
    }] subscribeNext:^(UIColor* value) {
        self.mapTextField.backgroundColor = value;
    }];
}

上面的代码的意思是,当输入的字符串长度超过3,就将字符串信号转换成颜色信号,然后订阅该颜色信号,并将颜色赋值给textField的背景色。这样的话,当字符串大于4,文本框的背景色变成了红色。

map

9、操作方法之组合

❶ concat
作用

按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。

底层实现
  1. 当拼接信号被订阅,就会调用拼接信号的didSubscribe
  2. didSubscribe中,会先订阅第一个源信号signalA
  3. 会执行第一个源信号signalAdidSubscribe
  4. 在第一个源信号signalAdidSubscribe中发送值,就会调用第一个源信号signalA订阅者的nextBlock
    通过拼接信号的订阅者把值发送出来。
  5. 在第一个源信号signalAdidSubscribe中发送完成,就会调用第一个源信号signalA订阅者的completedBlock,订阅第二个源信号signalB这时候才激活signalB
  6. 订阅第二个源信号signalB,执行第二个源信号signalBdidSubscribe
  7. 在第二个源信号signalA的didSubscribe中发送值,就会通过拼接信号的订阅者把值发送出来。
Demo演示
- (void)concatDemo
{
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"山川"];
        // 第一个信号必须要调用sendCompleted
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"河流"];
        return nil;
    }];
    
    // 创建组合信号
    // concat: 按顺序去连接
    RACSignal *concatSignal = [signalA concat:signalB];
    
    // 订阅组合信号
    [concatSignal subscribeNext:^(id x) {
    
        // 既能拿到A信号的值,又能拿到B信号的值
        NSLog(@"组合信号的值:%@",x);
    }];
}

输出结果为:

2020-09-16 18:25:03.475250+0800 框架Demo[61256:9660054] 组合信号的值:山川
2020-09-16 18:25:03.475394+0800 框架Demo[61256:9660054] 组合信号的值:河流
❷ then
作用

then方法会等待前面的信号中completed事件的发送完成,然后再订阅由then block返回的信号。这样就高效地把控制权从一个signal传递给下一个。

底层实现
  1. 先过滤掉之前的信号发出的值。
  2. 使用concat连接then返回的信号。
Demo演示
- (void)thenDemo
{
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        // 发送信号
        [subscriber sendNext:@"山川"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"河流"];
        return nil;
    }];
    
    // 创建组合信号
    // then: 忽略掉第一个信号所有值
    RACSignal *thenSignal = [signalA then:^RACSignal *{
        // 返回信号就是需要组合的信号
        return signalB;
    }];
    
    // 订阅信号
    [thenSignal subscribeNext:^(id x) {
        NSLog(@"组合信号值:%@",x);
    }];
}

输出结果为:

2020-09-16 18:25:32.523399+0800 框架Demo[61269:9661234] 组合信号值:河流
❸ merge
作用

把多个信号合并为一个信号,任何一个信号有新值的时候就会调用。

底层实现
  1. 合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
  2. 每发出一个信号,这个信号就会被订阅
  3. 也就是合并信号一被订阅,就会订阅里面所有的信号。
  4. 只要有一个信号被发出就会被监听。
Demo演示
- (void)mergeDemo
{
    // 任意一个信号请求完成都会订阅到
    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];
    
    // 组合信号
    RACSignal *mergeSignal = [signalA merge:signalB];
    
    // 订阅信号
    [mergeSignal subscribeNext:^(id x) {
        // 任意一个信号发送内容都会来这个block
        NSLog(@"信号值:%@",x);
    }];
    
    // 发送数据
    [signalA sendNext:@"abc"];
    [signalB sendNext:@"123"];
}

输出结果为:

2020-09-22 16:06:18.942371+0800 框架Demo[82453:17075493] 信号值:abc
2020-09-22 16:06:18.942508+0800 框架Demo[82453:17075493] 信号值:123
❹ zipWith
作用

把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。

底层实现
  1. 定义压缩信号,内部就会自动订阅signalAsignalB
  2. 每当signalA或者signalB发出信号,就会判断signalAsignalB有没有发出个信号,有就会把最近发出的信号都包装成元组发出。
Demo演示
- (void)zipWithDemo
{
    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];
    
    // 压缩成一个信号,等所有信号都发送内容的时候才会调用
    // 当一个界面多个请求的时候,要等所有请求完成才能更新UI
    RACSignal *zipSignal = [signalA zipWith:signalB];
    
    // 订阅信号
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 发送信号
    // 打印顺序是由压缩的信号顺序决定的,不是由发送信号的顺序决定的
    [signalB sendNext:@"123"];
    [signalA sendNext:@"abc"];
}

输出结果为:

2020-09-22 16:14:54.680677+0800 框架Demo[82572:17084745] 信号值为:<RACTwoTuple: 0x6000031c0280> (
    abc,
    123
)
❺ combine
作用

把两个信号组合成一个信号,跟zip一样,没什么区别。

底层实现
  1. 当组合信号被订阅,内部会自动订阅signalAsignalB,必须两个信号都发出内容,才会被触发。
  2. 并且把两个信号组合成元组发出。
Demo演示
- (void)combineDemo
{
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@1];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@2];
        return nil;
    }];
    
    // 把两个信号组合成一个信号,跟zip一样,没什么区别
    RACSignal *combineSignal = [signalA combineLatestWith:signalB];
        [combineSignal subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
}

输出结果为:

2020-09-22 16:20:38.777686+0800 框架Demo[82657:17091255] 信号值为:<RACTwoTuple: 0x6000035d0260> (
    1,
    2
)

想象一下,如果当textFieldtextView同时满足某个条件时,才能进行某项操作的话,应该如何写呢?RAC为我们准备了一个方法——combineLatest:reduce:信号合并。

- (void)combineTextFieldAndTextViewDemo
{
    RACSignal *mergeTwoSignal = [RACSignal combineLatest:@[self.combineTextField.rac_textSignal, self.combineTextView.rac_textSignal] reduce:^id(NSString * value1,NSString * value2) {
        
        return [NSNumber numberWithBool:([value1 isEqualToString:@"11111"] && [value2 isEqualToString:@"22222"])];
    }];
    
    RAC(self.combineButton, enabled) = [mergeTwoSignal map:^id(NSNumber* value) {
        return value;
    }];
}

- (void)clickCombineButton
{
    NSLog(@"齐天大圣");
}

上面的代码的意思是,当textField中的文字为"11111",同时textView中的文字为"22222"的时候,返回一个信号,信号的类型是NSNumber,然后通过转换器map,将值返回,返回的值用于确定按钮是否可用。

combine

10、操作方法之过滤

❶ filter
作用

使用它可以获取满足条件的信号。

Demo演示
- (void)filterDemo
{
    // 只有当我们文本框的内容长度大于5,才想要获取文本框的内容
    [[self.filterTextField.rac_textSignal filter:^BOOL(id value) {
        // value: 源信号的内容
        return [value length] > 5;
    }] subscribeNext:^(id x) {
        // 返回值就是过滤条件,只有满足这个条件才能获取到内容
        NSLog(@"过滤后的值为:%@",x);
    }];
}

输出结果为:

2020-09-22 16:47:58.559138+0800 框架Demo[83318:17131897] 过滤后的值为:123456
❷ ignore
作用

忽略某些值的信号。

Demo演示
- (void)ignoreDemo
{
    // ignore:忽略一些值
    // ignoreValues:忽略所有的值
    
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    // 2.忽略值
    RACSignal *ignoreSignal = [subject ignore:@"456"];
    // 3.订阅信号
    [ignoreSignal subscribeNext:^(id x) {
        NSLog(@"忽略后的值为:%@",x);
    }];
    
    // 4.发送数据
    [subject sendNext:@"2"];
    [subject sendNext:@"456"];
    [subject sendNext:@"789"];
}

输出结果为:

2020-09-22 16:51:23.320604+0800 框架Demo[83405:17137309] 忽略后的值为:2
2020-09-22 16:51:23.320997+0800 框架Demo[83405:17137309] 忽略后的值为:789
❸ distinctUntilChanged
作用

当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。

Demo演示
- (void)distinctUntilChangedDemo
{
    // distinctUntilChanged:如果当前的值跟上一个值相同,就不会被订阅到
    
    RACSubject *subject = [RACSubject subject];
    
    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"区别后的值为:%@",x);
    }];
    
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@2];
    [subject sendNext:@3];
}

输出结果为:

2020-09-22 16:51:23.320604+0800 框架Demo[83405:17137309] 忽略后的值为:2
2020-09-22 16:51:23.320997+0800 框架Demo[83405:17137309] 忽略后的值为:789
❹ take
作用

从开始一共取N次的信号。

Demo演示
- (void)takeDemo
{
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 2、处理信号,订阅信号
    [[subject take:2] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
    
    // 3.发送信号
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
}

输出结果为:

2020-09-22 17:34:03.475913+0800 框架Demo[83895:17167666] 信号值为:1
2020-09-22 17:34:03.476073+0800 框架Demo[83895:17167666] 信号值为:2
❺ takeLast
作用

取最后N次的信号,前提条件是订阅者必须调用完成,因为只有完成,才知道总共有多少信号。

Demo演示
- (void)takeLastDemo
{
    // 1.创建信号
    RACSubject *signal = [RACSubject subject];
    
    // 2、处理信号,订阅信号
    [[signal takeLast:1] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
    
    // 3.发送信号
    [signal sendNext:@1];
    [signal sendNext:@2];
    [signal sendNext:@3];
    
    [signal sendCompleted];
}

输出结果为:

2020-09-22 17:37:03.683675+0800 框架Demo[83955:17170666] 信号值为:3
❻ takeUntil
作用

获取信号直到执行完这个信号,当发送信号为空或结束时,后面有发送的信号,也不会执行。

Demo演示
- (void)takeUntilDemo
{
    RACSubject *subject = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    
    [[subject takeUntil:signal] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
    
    [subject sendNext:@1];
    [subject sendNext:@"abc"];
    [signal sendError:nil];
    [signal sendNext:@2];
    [signal sendNext:@3];
}

输出结果为:

2020-09-22 17:40:08.429672+0800 框架Demo[84011:17173856] 信号值为:1
2020-09-22 17:40:08.429799+0800 框架Demo[84011:17173856] 信号值为:abc
❼ skip
作用

跳过几个信号,不接受。

Demo演示
- (void)skipDemo
{
    // skip;跳跃几个值
    RACSubject *subject = [RACSubject subject];
    
    [[subject skip:2] subscribeNext:^(id x) {
        NSLog(@"跳过后的值为:%@",x);
    }];
    
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
}

输出结果为:

2020-09-22 17:42:35.597149+0800 框架Demo[84054:17176772] 跳过后的值为:3

11、操作方法之秩序

// doNext和doCompleted的使用
- (void)orderDemo
{
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@123];
        [subscriber sendCompleted];
        return nil;
    }];
    
    [[[signal doNext:^(id x) {
        
        // 执行[subscriber sendNext:@123];之前会调用这个Block
        NSLog(@"doNext");
    }] doCompleted:^{
        
        // 执行[subscriber sendCompleted];之前会调用这个Block
        NSLog(@"doCompleted");
    }] subscribeNext:^(id x) {
        
        NSLog(@"信号值为:%@",x);
    }];
}

输出结果为:

2020-09-22 17:47:26.881166+0800 框架Demo[84129:17181460] doNext
2020-09-22 17:47:26.881297+0800 框架Demo[84129:17181460] 信号值为:123
2020-09-22 17:47:26.881406+0800 框架Demo[84129:17181460] doCompleted

在不改变信号的基础上,进行一些附加的操作,比如说在订阅到给label赋值前,改变label的背景色。


运行效果如下:

donext

12、操作方法之线程

RACSchedulerRAC中用GCD封装的队列。因为信号的流转及操作都是在block中完成的,也就是说大部分操作都是在子线程中执行的操作,但是有时候需要回到主线程完成一些事情,比如,请求到数据后,要刷新UI,这就必须回到主线程[RACScheduler mainThreadScheduler]RAC提供了这样的方法deliverOn

文本框
// deliverOn的使用
- (void)deliverOnDemo
{
    @weakify(self)
    [[[[[self autoDeleteSubscription] then:^RACSignal *{
        @strongify(self);
        
        return self.RACSchedulerTextField.rac_textSignal;
        
    }] filter:^BOOL(NSString* value) {
        
        return value.length > 3 ? YES : NO;
        
    }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSString * value) {
        @strongify(self);
        
        // 回到主线程更新UI
        self.RACSchedulerLabel.text = value;
        NSLog(@"当前线程为:%@,value为:%@",[NSThread currentThread],value);
        
    } error:^(NSError * _Nullable error) {
        NSLog(@"出错了:%@",error);
    }];
}

输出结果为:

2020-09-16 15:00:18.771928+0800 框架Demo[58376:9488070] 当前线程为:<NSThread: 0x6000028e8b80>{number = 1, name = main},value为:1234
2020-09-16 15:00:19.098310+0800 框架Demo[58376:9488070] 当前线程为:<NSThread: 0x6000028e8b80>{number = 1, name = main},value为:12345

这样的话,实现的效果就是,在textField中输入文字而且当文字大于3的时候,会在label中显示出来,而且可以看到订阅信号的block中打印出来的线程是主线程。

网络请求和图片加载
运行效果
RACScheduler
使用系统的方法加载图片
- (void)requestImage
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        
        NSURL *imageURL = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2789775069,1607374561&fm=26&gp=0.jpg"];
        
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
        if (image != nil)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.RACSchedulerImageView.image = image;
            });
        }
    });
}
使用RAC方法请求图片

创建一个加载图片的方法,方法返回的是RACSignle信号对象:

- (RACSignal *)RACRequestImage
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSURL *imageURL = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2789775069,1607374561&fm=26&gp=0.jpg"];
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
        
        [subscriber sendNext:image];
        [subscriber sendCompleted];
        
        return nil;
    }];
}
使用RAC方法加载图片
self.RACSchedulerButton = [[UIButton alloc] initWithFrame:CGRectMake(120, 720, 200, 100)];
self.RACSchedulerButton.backgroundColor = [UIColor blackColor];
[self.RACSchedulerButton setTitle:@"请求图片" forState:UIControlStateNormal];
@weakify(self);
[[[[self.RACSchedulerButton rac_signalForControlEvents:(UIControlEventTouchUpInside)]
   flattenMap:^RACSignal *(id value) {
    
    return [self RACRequestImage];
    
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(UIImage * image) {
    @strongify(self);
    
    self.RACSchedulerImageView.image = image;
}];
[self.view addSubview:self.RACSchedulerButton];

13、操作方法之时间

❶ timeout
作用

超时,可以让一个信号在一定的时间后,自动报错。

Demo演示
- (void)timeoutDemo
{
    RACSignal *signalA = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@123];
        });
        return nil;
    }] timeout:1 onScheduler:[RACScheduler currentScheduler]];
    
    
    [signalA subscribeNext:^(id x) {
    
        // 模拟2秒后才会发送消息
        NSLog(@"信号值为:%@",x);
    } error:^(NSError *error) {
        // 1秒后超时会自动调用
        NSLog(@"错误为:%@",error);
    }];
}

输出结果为:

2020-09-22 18:01:23.179490+0800 框架Demo[84311:17192607] 错误为:Error Domain=RACSignalErrorDomain Code=1 "(null)"
❷ interval
作用

每隔一段时间发出信号。

Demo演示
- (void)intervalDemo
{
    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@123];
        return nil;
    }] delay:2.0];// delay延迟发送next
    
    [signal subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
}

输出结果为:

2020-09-22 18:05:17.417663+0800 框架Demo[84383:17196230] 信号值为:123

14、操作方法之重复

❶ retry
作用

只要失败,就会重新执行创建信号中的block,直到成功。

Demo演示
- (void)retryDemo
{
    __block int i = 0;
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        if (i == 5)
        {
            [subscriber sendNext:@"谢佳培"];
        }
        else
        {
            NSLog(@"这名字不好听");
            [subscriber sendError:nil];
        }
    
        I++;
    
        return nil;
    }];
    
    [[signal retry] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    } error:^(NSError *error) {
        NSLog(@"错误信息为:%@",error);
    }];
}

输出结果为:

2020-09-22 18:09:34.601954+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605102+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605266+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605386+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605497+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605635+0800 框架Demo[84444:17200030] 信号值为:谢佳培
❷ replay
作用

当一个信号被多次订阅,反复播放内容。

Demo演示
- (void)replayDemo
{
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        [subscriber sendNext:@123];
        [subscriber sendNext:@456];
        return nil;
    }];
    
    [[signal replay] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
    
    [[signal replay] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
}

输出结果为:

2020-09-22 18:15:07.998935+0800 框架Demo[84523:17205138] 信号值为:123
2020-09-22 18:15:07.999077+0800 框架Demo[84523:17205138] 信号值为:456
2020-09-22 18:15:07.999240+0800 框架Demo[84523:17205138] 信号值为:123
2020-09-22 18:15:07.999328+0800 框架Demo[84523:17205138] 信号值为:456
❸ throttle
作用

当某个信号发送比较频繁时,可以使用节流,如果前面信号在设定的时间内没有变化时,throttle就会把信号传到下面的事件中去。用文本框textField作比喻,当我在里面输入字符时,subscribeNextblock会不停的走,每输入一个字符,就会走一遍。如果我想在输入过程中不需要每改变一个字符就走一遍,而是等输入完成或停止的时候再走block里面的代码,那就可以用throttle

Demo演示
- (void)throttleDemo
{
    RACSubject *signal = [RACSubject subject];
    _signal = signal;

    // 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出
    [[signal throttle:1.0 valuesPassingTest:^BOOL(id next) {
        return YES;
    }] subscribeNext:^(id x) {
        NSLog(@"信号值为:%@",x);
    }];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [_signal sendNext:@(i)];
    I++;
}

输出结果为:

2020-09-22 18:21:08.508250+0800 框架Demo[84618:17210393] 信号值为:0
2020-09-22 18:21:10.762703+0800 框架Demo[84618:17210393] 信号值为:1
2020-09-22 18:21:13.057885+0800 框架Demo[84618:17210393] 信号值为:2
2020-09-22 18:21:15.037305+0800 框架Demo[84618:17210393] 信号值为:3
2020-09-22 18:21:16.995578+0800 框架Demo[84618:17210393] 信号值为:4

15、RAC双向绑定

何谓双向绑定?例如两个UITextFidle,任意一个UITextFidle输入文本变化时,另一个也要跟着变化。代码很简单,RACUITextFidle添加了分类方法- (RACChannelTerminal *)rac_newTextChannel;,可以很简单的去生成RACChannelTerminal,来实现双向绑定。同样也给UITextViewUISwitchUIStepperUISliderUISegmentedControlUIControlUIDatePicker控件添加了相应的分类方法去生成RACChannelTerminal

上面例子中的代码也很简单:

- (void)channelDemo
{
    [self.channelFirstTextField.rac_newTextChannel subscribe:self.channelSecondTextField.rac_newTextChannel];
    
    [self.channelSecondTextField.rac_newTextChannel subscribe:self.channelFirstTextField.rac_newTextChannel];
}

运行效果如下:

channel
❶ 什么是RACChannel和RACChannelTerminal呢?

RACChannel可以看成是一个双向通道,由两个并行工作的可控信号组成。而RACChannelTerminal则是这个双向通道的一端。可以简单理解为,两个属性双向监听,相当于在这两个属性直接建立个一个通道,而其中的一端就是RACChannelTerminal。类比于网络编程里面socket的概念,RACChannel类似网络链接通道,RACChannelTerminal类似于socket

❷ 直接使用rac_newTextChannel实现UITextField的双向绑定是有隐患的
- (void)rac_newTextChannelBug
{
    // 使用RACChannelTo双向绑定
    RACChannelTo(self, channelString) = self.channelFirstTextField.rac_newTextChannel;
    
    // 监控键盘输入时候两个值的变化
    [self.channelFirstTextField.rac_textSignal subscribeNext:^(id x) {
        NSLog(@"channelString的值为:%@",self.channelString);
        NSLog(@"channelFirstTextField的文本值为:%@",x);
    }];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self createChannelSubviews];
    [self rac_newTextChannelBug];
    self.channelString = @"888888";// string改变
    // self.channelFirstTextField.text = @"888888";// textField.text 改变
    NSLog(@"channelString的值为:%@",self.channelString);
    NSLog(@"channelFirstTextField的文本值为:%@",self.channelFirstTextField.text);
}

㊀ 通过键盘给textField.text赋值的情况。

均注释
// self.channelString = @"888888";
// self.channelFirstTextField.text = @"888888";

输出结果显示textField.textstring均改变:

2020-09-23 11:33:17.312011+0800 框架Demo[86497:17335341] channelString的值为:1
2020-09-23 11:33:17.312148+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:1
2020-09-23 11:33:17.662046+0800 框架Demo[86497:17335341] channelString的值为:12
2020-09-23 11:33:17.662146+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:12
2020-09-23 11:33:18.011832+0800 框架Demo[86497:17335341] channelString的值为:123
2020-09-23 11:33:18.011933+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:123
2020-09-23 11:33:19.833403+0800 框架Demo[86497:17335341] channelString的值为:1234
2020-09-23 11:33:19.833493+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:1234

㊁ 通过代码赋值,打开改变string的注释。输出结果显示textField.textstring均改变。

2020-09-23 11:37:31.968805+0800 框架Demo[86561:17339061] channelString的值为:(null)
2020-09-23 11:37:31.968918+0800 框架Demo[86561:17339061] channelFirstTextField的文本值为:
2020-09-23 11:37:31.971041+0800 框架Demo[86561:17339061] channelString的值为:888888
2020-09-23 11:37:31.971151+0800 框架Demo[86561:17339061] channelFirstTextField的文本值为:888888

㊂ 通过代码赋值,打开改变textField.text的注释。输出结果显示string不改变,textField.text改变。

2020-09-23 11:39:28.120200+0800 框架Demo[86601:17341391] channelString的值为:(null)
2020-09-23 11:39:28.120353+0800 框架Demo[86601:17341391] channelFirstTextField的文本值为:
2020-09-23 11:39:28.122432+0800 框架Demo[86601:17341391] channelString的值为:(null)
2020-09-23 11:39:28.122565+0800 框架Demo[86601:17341391] channelFirstTextField的文本值为:888888
❸ 直接使用RACChannelTo实现UITextField的双向绑定是有隐患的
- (void)RACChannelToBug
{
    // 使用RACChannelTo双向绑定
    RACChannelTo(self, channelString) = RACChannelTo(self.channelSecondTextField, text);
    
    // 监控键盘输入时候两个值的变化
    [self.channelSecondTextField.rac_textSignal subscribeNext:^(id x) {
        NSLog(@"channelString的值为:%@",self.channelString);
        NSLog(@"channelSecondTextField的文本值为:%@",x);
    }];
}

㊀ 通过键盘给textField.text赋值的情况。均注释,输出结果显示textField.text改变,而string不改变。

2020-09-23 13:40:50.986948+0800 框架Demo[87869:17401966] channelFirstTextField的文本值为:1
2020-09-23 13:40:51.274946+0800 框架Demo[87869:17401966] channelString的值为:
2020-09-23 13:40:51.275046+0800 框架Demo[87869:17401966] channelFirstTextField的文本值为:12
2020-09-23 13:40:51.679521+0800 框架Demo[87869:17401966] channelString的值为:
2020-09-23 13:40:51.679617+0800 框架Demo[87869:17401966] channelFirstTextField的文本值为:123
2020-09-23 13:40:53.389484+0800 框架Demo[87869:17401966] channelString的值为:

㊁ 通过代码赋值,打开改变string的注释。输出结果显示textField.textstring均改变。

2020-09-23 13:42:44.182341+0800 框架Demo[87901:17403933] channelString的值为:
2020-09-23 13:42:44.182438+0800 框架Demo[87901:17403933] channelFirstTextField的文本值为:
2020-09-23 13:42:44.184201+0800 框架Demo[87901:17403933] channelString的值为:888888
2020-09-23 13:42:44.184306+0800 框架Demo[87901:17403933] channelFirstTextField的文本值为:888888

㊂ 通过代码赋值,打开改变textField.text的注释。输出结果显示textField.textstring均改变。

2020-09-23 13:43:16.926716+0800 框架Demo[87913:17404857] channelString的值为:
2020-09-23 13:43:16.926805+0800 框架Demo[87913:17404857] channelFirstTextField的文本值为:
2020-09-23 13:43:16.928547+0800 框架Demo[87913:17404857] channelString的值为:888888
2020-09-23 13:43:16.928644+0800 框架Demo[87913:17404857] channelFirstTextField的文本值为:888888
❹ 问题总结

无论是使用RACChannelTo还是rac_newTextChannelstring的值改变的时候,textField.text都会跟着改变,也就是说string -> textField.text 的这个单向通道是没问题的。

问题就在于无论是通过键盘或者代码赋值来改变textField.text的值的时候,我们都要让绑定的string对应的改变,这才是我们要的双向绑定。

rac_newTextChannel:当通过键盘改变textField. text的值的时候才会把这个值发送出去。
RACChannelTo:当通过代码改变textField.text的值的时候才会把这个值发送出去。

❺ 正确解决方案
- (void)rightChannelDemo
{
    RACChannelTo(self, channelString) = RACChannelTo(self.channelFirstTextField, text);
    
    @weakify(self);
    [self.channelFirstTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        @strongify(self);
        self.channelString = x;
        NSLog(@"channelString的值为:%@",self.channelString);
        NSLog(@"channelFirstTextField的文本值为:%@",x);
    }];
}

简化代码:

- (void)rightChannelSimpleDemo
{
    RACChannelTo(self, channelString) = RACChannelTo(self.channelFirstTextField, text);
    [self.channelFirstTextField.rac_textSignal subscribe:RACChannelTo(self, channelString)];
}

㊀ 通过键盘给textField.text赋值的情况。输出结果显示textField.textstring均改变。

2020-09-23 13:49:32.150812+0800 框架Demo[87998:17410375] channelString的值为:1
2020-09-23 13:49:32.150917+0800 框架Demo[87998:17410375] channelFirstTextField的文本值为:1
2020-09-23 13:49:32.388977+0800 框架Demo[87998:17410375] channelString的值为:12
2020-09-23 13:49:32.389083+0800 框架Demo[87998:17410375] channelFirstTextField的文本值为:12
2020-09-23 13:49:32.688164+0800 框架Demo[87998:17410375] channelString的值为:123
2020-09-23 13:49:32.688271+0800 框架Demo[87998:17410375] channelFirstTextField的文本值为:123

㊁ 通过代码赋值,打开改变string的注释。输出结果显示textField.textstring均改变。

2020-09-23 13:50:43.195027+0800 框架Demo[88028:17412036] channelString的值为:
2020-09-23 13:50:43.195181+0800 框架Demo[88028:17412036] channelFirstTextField的文本值为:
2020-09-23 13:50:43.197267+0800 框架Demo[88028:17412036] channelString的值为:888888
2020-09-23 13:50:43.197393+0800 框架Demo[88028:17412036] channelFirstTextField的文本值为:888888

㊂ 通过代码赋值,打开改变textField.text的注释。输出结果显示textField.textstring均改变。

2020-09-23 13:51:17.817595+0800 框架Demo[88045:17414054] channelString的值为:
2020-09-23 13:51:17.817716+0800 框架Demo[88045:17414054] channelFirstTextField的文本值为:
2020-09-23 13:51:17.819569+0800 框架Demo[88045:17414054] channelString的值为:888888
2020-09-23 13:51:17.819679+0800 框架Demo[88045:17414054] channelFirstTextField的文本值为:888888

满足上面三点就算完全的双向绑定。UITextView的情况及处理跟UITextField一样。

❻ 错误解决方案:使用rac_textSignal
- (void)wrongChannelDemo
{
    RACChannelTo(self, channelString) = self.channelFirstTextField.rac_newTextChannel;
    
    // 当textField.text改变的时候,会回调这个block,然后再给string赋值,实现双向绑定
    @weakify(self);
    [self.channelFirstTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        @strongify(self);
        self.channelString = x;
        NSLog(@"channelString的值为:%@",self.channelString);
        NSLog(@"channelFirstTextField的文本值为:%@",x);
    }];
}

通过代码赋值,打开改变textField.text的注释。输出结果显示textField.text改变,而string不改变。

2020-09-23 13:58:12.239358+0800 框架Demo[88234:17423565] channelString的值为:
2020-09-23 13:58:12.239473+0800 框架Demo[88234:17423565] channelFirstTextField的文本值为:
2020-09-23 13:58:12.241037+0800 框架Demo[88234:17423565] channelString的值为:
2020-09-23 13:58:12.241145+0800 框架Demo[88234:17423565] channelFirstTextField的文本值为:888888

代码赋值的时候,textField.rac_textSignal不会触发回调,原因 是rac_textSignal这个signal是监听UIControlEventAllEditingEvents的,所以对setter方式不会触发signal

进入rac_textSignal的源码实现可以看到:

concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]

concat:连接信号,第一个信号必须发送完成,第二个信号才会被激活。也就是说,必须触发UIControlEventAllEditingEvents才会触发rac_textSignal,所以用setter的方式赋值就不行了。

❼ 仍然存在的问题及解决方案
RACChannelTo(model, string) = textField.rac_newTextChannel; // ui 到 model 的绑定
RACChannelTo(textField, text) = RACChannelTo(model, string); // model 到 ui 的绑定

但是用RACChannelTo的方式实现双向绑定有一个痛点:如果要绑定model中的基本数据类型(age, price,count....),该如何操作?用rac提供的map方法会返回一个RACSignal类型,并不能再次转化为RACChannelTerminal类型。

换一种做法是不使用RACChannel😂,之前的白说了吗?。而是直接用RAC()RACObeserve()宏来实现。

// model - > ui
RAC(name_tf, text) = RACObserve(_person, name);
RAC(age_tf, text) = [RACObserve(_person, age) map:^id _Nullable(id _Nullable value) {
return [value description];
}];

// ui -> model
RAC(_person, name) = name_tf.rac_textSignal;
RAC(_person, age) = age_tf.rac_textSignal;

二、ReactiveCocoa的源码解析

本着饮水思源的想法,面对ReactiveCocoa的强大功能,按捺不住心中的好奇心,于是走进其源码之中,一探ReactiveCocoa的魅力所在。虽然,耳闻其强大功能的核心是:信号,但一直不知道这个信号是如何产生、如何传递,又是如何被处理的。曾经以为信号传递是通知,但是真正读了源码后,才发现之前的想法有多不妥,而人家的实现又是多巧妙。

1、RACSignal

a、创建信号
RACSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}
RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    // 创建了一个RACDynamicSignal类的信号
    RACDynamicSignal *signal = [[self alloc] init];
    // 将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}
b、订阅信号
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock {
    NSCParameterAssert(nextBlock != NULL);
    NSCParameterAssert(completedBlock != NULL);
    
    // 内部创建了RACSubscriber(订阅者)类的实例对象o,并且将nextBlock保存到o中
    // 在返回值处执行o,实际也是执行了nextBlock
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:completedBlock];
    return [self subscribe:o];
}
RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];
    
    // 将block保存到subscriber中
    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}
RACDynamicSignal.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    // 判断有无self.didSubscribe
    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            // 有则执行该self.didSubscribe,意味着将订阅者subscriber发送过去
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}
RACPassthroughSubscriber.m
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
    NSCParameterAssert(subscriber != nil);

    self = [super init];

    // 保存订阅者,信号,处理操作
    _innerSubscriber = subscriber;
    _signal = signal;
    _disposable = disposable;

    [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
    return self;
}
c、发送信号
RACSubscriber.m
- (void)sendNext:(id)value {
    @synchronized (self) {
        // 名为next的block是返回值为void,参数为id类型的value
        // 在sendNext:内部,将next复制给nextBlock
        void (^nextBlock)(id) = [self.next copy];
        
        if (nextBlock == nil) return;
        
        // 执行该方法后,subscribeNext:的block参数才会被调用
        nextBlock(value);
    }
}
d、流程总结
  1. 创建信号:首先把didSubscribe保存到信号中,还不会触发。
  2. 订阅信号:当信号被订阅,也就是调用signalsubscribeNext:nextBlock
    2.1 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
    2.2 subscribeNext内部会调用siganldidSubscribe
  3. 发送数据:siganldidSubscribe中调用[subscriber sendNext:@1];
    3.1 sendNext底层其实就是执行subscribernextBlock

2、RACSubject

RACSubscriber表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。

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

a、创建信号
RACSubject.m
+ (instancetype)subject {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (self == nil) return nil;

    _disposable = [RACCompoundDisposable compoundDisposable];
    _subscribers = [[NSMutableArray alloc] initWithCapacity:1];
    
    return self;
}
b、订阅信号

RACSubject订阅信号的实质就是将内部创建的订阅者保存在订阅者数组self.subscribers中,仅此而已。订阅者对象有一个名为nextBlockblock参数。

RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}
RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];
    
    // 将block保存到subscriber中
    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}
RACSubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }
    
    [disposable addDisposable:[RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            // Since newer subscribers are generally shorter-lived, search
            // starting from the end of the list.
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }]];

    return disposable;
}
c、发送信号
- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
    NSArray *subscribers;
    
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }
    
    // 1. 先遍历订阅者数组中的订阅者
    for (id<RACSubscriber> subscriber in subscribers) {
        // 2. 后执行订阅者中的nextBlock
        // 3. 最后让订阅者发送信号
        block(subscriber);
    }
}
d、流程总结
流程
  1. 创建信号。
    1.1 同时创建subscribers数组来保存订阅者。
  2. 调用subscribeNext订阅信号。
    2.1 内部创建subscriber订阅者并保存到subscribers
    2.2 订阅者对象中有个nextBlock并且已经赋值了。
  3. 发送数据。订阅者对象调用sendNext发送信号。
  4. 遍历刚刚保存的所有订阅者,一个接一个调用订阅者的nextBlock
注意
  • 由于本质是将订阅者保存到数组中,所以可以有多个订阅者订阅信息。
  • 必须先订阅,后发送信息。订阅信号就是创建订阅者的过程,如果不先订阅,数组中就没有订阅者对象,那就无法通过订阅者发送消息。

3、RACReplaySubject

RACReplaySubjectRACSubject的子类。由于每次发送信号时,会先保存nextBlock,然后调用父类的sendNext方法,遍历订阅者,执行信号;而每次订阅信号时,会从valuesReceived中取值,然后调用sendNext方法,遍历订阅者,执行信号。所以,订阅和发送没有先后顺序。

a、创建信号
RACSubject.m
+ (instancetype)subject {
    return [[self alloc] init];
}
RACReplaySubject.m
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
    return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}

- (instancetype)init {
    return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}

- (instancetype)initWithCapacity:(NSUInteger)capacity {
    self = [super init];
    
    _capacity = capacity;
    
    // 会用这个数组保存值value
    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);
    
    return self;
}
b、订阅信号
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}
RACReplaySubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;

                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            if (compoundDisposable.disposed) return;

            if (self.hasCompleted) {
                [subscriber sendCompleted];
            } else if (self.hasError) {
                [subscriber sendError:self.error];
            } else {
                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
                [compoundDisposable addDisposable:subscriptionDisposable];
            }
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}
c、发送信号
- (void)sendNext:(id)value {
    @synchronized (self) {
        // 发送信号的时候,会先将值value保存到数组中
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
        
        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
        
        // 调用父类发送(先遍历订阅者,然后发送值value)
        [super sendNext:value];
    }
}
d、流程总结
  1. 创建信号。
    1.1 同时创建subscribers数组来保存订阅者。
  2. 调用subscribeNext订阅信号。
    2.1 内部创建subscriber订阅者并保存到subscribers
    2.2 订阅者对象中有个nextBlock并且已经赋值了。
  3. 遍历self.valuesReceived,然后给subscribers发送。
  4. 订阅者对象调用sendNext发送信号。
  5. 保存订阅者的nextBlockself.valuesReceived
  6. 调用父类的sendNext遍历订阅者,执行nextBlock

4、RACMulticastConnection

RACMulticastConnection用于当一个信号被多次订阅时,为了保证创建信号时,避免多次调用创建信号的block造成副作用,可以使用该类处理,保证创建信号的block执行一次。

a、创建信号
RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    // 创建了一个RACDynamicSignal类的信号
    RACDynamicSignal *signal = [[self alloc] init];
    // 将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}
b、创建连接
RACSignal+Operations.m
- (RACMulticastConnection *)publish {
    // 创建订阅者
    RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];

    // 创建connection,参数是刚才创建的订阅者
    RACMulticastConnection *connection = [self multicast:subject];
    return connection;
}

- (RACMulticastConnection *)multicast:(RACSubject *)subject {
    [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
    RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
    return connection;
}
RACMulticastConnection.m
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
    NSCParameterAssert(source != nil);
    NSCParameterAssert(subject != nil);

    self = [super init];

    // 保存原始信号
    _sourceSignal = source;
    
    _serialDisposable = [[RACSerialDisposable alloc] init];
    
    // 保存订阅者,即_signal是RACSubject对象
    _signal = subject;
    
    return self;
}
c、订阅信号
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}
RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];
    
    // 将block保存到subscriber中
    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}
RACSubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }
    
    [disposable addDisposable:[RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            // Since newer subscribers are generally shorter-lived, search
            // starting from the end of the list.
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }]];

    return disposable;
}
d、连接信号
RACMulticastConnection.m
- (RACDisposable *)connect {
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

    if (shouldConnect) {
        // 订阅原生信号
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
    }

    return self.serialDisposable;
}
RACDynamicSignal.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    // 判断有无self.didSubscribe
    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            // 有则执行该self.didSubscribe,意味着将订阅者subscriber发送过去
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}
RACSubject.m
- (void)sendNext:(id)value {
    // 遍历_subscribers数组,执行nextBlock
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}
e、流程总结
  1. 创建connect
    1.1 connect.sourceSignal -> RACSignal(原始信号)
    1.2 connect.signal -> RACSubject
  2. 订阅connect.signal
    2.1 会调用RACSubjectsubscribeNext,创建订阅者,并且把订阅者保存起来,不会执行block
  3. [connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
    3.1 订阅原始信号,就会调用原始信号中的didSubscribe
    3.2 通过didSubscribe拿到订阅者再调用sendNext,其实是调用RACSubjectsendNext
  4. RACSubjectsendNext会遍历RACSubject所有订阅者并发送信号。
    4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock

5、RACCommand

RACCommand类用来表示动作的执行,是对动作触发后的连锁事件的封装。常用在封装网络请求,按钮点击等等场合。RACCommand用来封装事件时,还可以订阅信号(executionSignals)、订阅最新信号(switchToLatest)、跳过几次信号(skip)、判断信号是否正在执行(executing),在执行信号时,还可以监听错误信号和完成信号。

a、创建command
RACCommand.m
- (instancetype)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal<id> * (^)(id input))signalBlock {
    NSCParameterAssert(signalBlock != nil);

    self = [super init];

    _addedExecutionSignalsSubject = [RACSubject new];
    _allowsConcurrentExecutionSubject = [RACSubject new];
    
    // 保存创建信号的block
    _signalBlock = [signalBlock copy];
}
b、执行command
RACCommand.m
- (RACSignal *)execute:(id)input {
    BOOL enabled = [[self.immediateEnabled first] boolValue];
    if (!enabled) {
        NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
            NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
            RACUnderlyingCommandErrorKey: self
        }];

        return [RACSignal error:error];
    }

    RACSignal *signal = self.signalBlock(input);
    NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
    
    // 创建连接,用RACReplaySubject作为订阅者
    RACMulticastConnection *connection = [[signal
        subscribeOn:RACScheduler.mainThreadScheduler]
        multicast:[RACReplaySubject subject]];
    
    [self.addedExecutionSignalsSubject sendNext:connection.signal];

    // 连接信号
    [connection connect];
    return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)];
}
RACMulticastConnection.m
- (RACDisposable *)connect {
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

    if (shouldConnect) {
        // 订阅原生信号
        // 执行创建信号的block
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
    }

    return self.serialDisposable;
}
c、订阅command
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}
RACReplaySubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;

                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            if (compoundDisposable.disposed) return;

            if (self.hasCompleted) {
                [subscriber sendCompleted];
            } else if (self.hasError) {
                [subscriber sendError:self.error];
            } else {
                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
                [compoundDisposable addDisposable:subscriptionDisposable];
            }
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}

Demo

Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo

参考文献

iOS ReactiveCocoa框架的简单使用
RAC双向绑定UITextField的正确姿势
RAC中双向监听回声问题

相关文章

  • IOS框架:Reactive Cocoa

    原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...

  • iOS Reactive Cocoa使用浅析

    Reactive Cocoa浅析 ReactiveCocoa是响应式编程(FRP)在IOS中的一个实现框架,Git...

  • iphone开发 IOS 组织架构图

    Cocoa框架是iOS应用程序的基础,了解Cocoa框架,对开发ios应用有很大的帮助。 1、Cocoa是什么? ...

  • IOS 整体框架类图值得收藏

    Cocoa框架是iOS应用程序的基础,了解Cocoa框架,对开发iOS应用有很大的帮助。 1、Cocoa是什么? ...

  • iOS中Cocoa框架·Runtime及isa指针知识·填坑

    Cocoa框架是iOS应用程序的基础,了解Cocoa框架,对开发iOS应用有很大的帮助。 1、Cocoa是什么? ...

  • IOS RAC(Reactive Cocoa)

    导入RAC 使用CocoaPods直接导入 pod 'ReactiveObjC' 万事万物皆信号,任何事情都是通过...

  • Cocoa Touch 框架

    Cocoa Touch 框架是iOS开发的基础,了解Cocoa Touch框架,对我们开发iOS软件有很大的帮助。...

  • iOS整体框架及类继承体系

    iOS整体框架 通常我们称iOS的框架为cocoa框架.话不多说,官方的整体框架图如下: 简单解释一下:Cocoa...

  • 相关CocoaTouch层学习

    iOS – Cocoa Touch简介: iOS 应用程序的基础 Cocoa Touch 框架重用了许多 Mac ...

  • Reactive Cocoa

    潜在的内存泄漏及解决方案 1.一定使用@weakify和@strongify在block里没有很直观的看到self...

网友评论

      本文标题:IOS框架:Reactive Cocoa

      本文链接:https://www.haomeiwen.com/subject/uoitvktx.html