RAC(ReactiveCocoa)的基本使用

3,035 阅读7分钟

RAC简介

一. 什么是ReactiveCocoa?

ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。

ReactiveCocoa 的宗旨是Streams of values over time ,随着时间变化而不断流动的数据流。

ReactiveCocoa 主要解决了以下这些问题:

  • UI数据绑定

    • UI控件通常需要绑定一个事件,RAC可以很方便的绑定任何数据流到控件上。
  • 用户交互事件绑定

    • RAC为可交互的UI控件提供了一系列能发送Signal信号的方法。这些数据流会在用户交互中相互传递。
  • 解决状态以及状态之间依赖过多的问题

    • 有了RAC的绑定之后,可以不用在关心各种复杂的状态,isSelect,isFinish……也解决了这些状态在后期很难维护的问题。
  • 消息传递机制的大统一

    • OC中编程原来消息传递机制有以下几种:Delegate,Block Callback,Target-Action,Timers,KVO,objc上有一篇关于OC中这5种消息传递方式改如何选择的文章Communication Patterns,推荐大家阅读。现在有了RAC之后,以上这5种方式都可以统一用RAC来处理。 其实也就是一下的这些消息传递机制。RAC都用了统一的方式封装起来让我们更加方便的去使用。

RAC的常用方法

1.通知NSNotificationCenter

  • 普通写法
    • 添加键盘弹出的通知addObserver
    [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillShow:)
                                                     name:UIKeyboardWillShowNotification
                                                   object:nil];
    
    • 实现方法
    - (void) keyboardWillShow:(NSNotification *)note {
        NSLog(@"键盘弹出了");
    }
    
    • 移除通知
    - (void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    }
    
  • RAC 写法
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@",x);
    }];
    
    • x的打印结果
    NSConcreteNotification 0x6000000c5170 {name = UIKeyboardWillShowNotification; userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 7;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {390, 336}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {195, 1012}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {195, 676}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 844}, {390, 336}}";
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 508}, {390, 336}}";
    UIKeyboardIsLocalUserInfoKey = 1;
    }}
    

2.KVO

  • 普通写法

    • 模型
    @interface TestModel : NSObject
    @property (assign, nonatomic) int age;
    @property (assign, nonatomic) int height;
    @end
    
    
    #import "KVOViewController.h"
    #import "TestModel.h"
    
    @interface KVOViewController ()
    @property (strong, nonatomic) TestModel *testModel1;
    @end
    
    @implementation KVOViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.testModel1 = [[TestModel alloc] init];
        self.testModel1.age = 1;
        self.testModel1.height = 11;
    
    
        // 给testModel1对象添加KVO监听 当KVO监听age属性变化
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [self.testModel1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.testModel1.age = 20;
    
        self.testModel1.height = 30;
    }
    
    
    // 当监听对象的属性值发生改变时,就会调用
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    - (void)dealloc {
        [self.testModel1 removeObserver:self forKeyPath:@"age"];
        [self.testModel1 removeObserver:self forKeyPath:@"height"];
    }
    
    • RAC写法 #define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
    [RACObserve(self.testModel1, age)subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
    

3.代理 Delegate

  • 普通写法
1. 遵守代理 <UITextFieldDelegate> 

2. 空间 @property (weak, nonatomic) IBOutlet UITextField *textField;


3. 代理的响应者 self.textField.delegate = self;


4.代理方法
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    NSLog(@"开始编辑");
}
  • RAC写法
    self.textField.delegate = self;
    
   [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple * _Nullable x) {
        NSLog(@"%@",x);
    }];
    
    
    <RACTuple: 0x600000dd9130> (
    "<UITextField: 0x127d0bf90; frame = (97 118; 220 34); text = ''; opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000001e6f70>; borderStyle = RoundedRect; background = <_UITextFieldSystemBackgroundProvider: 0x600000f8eb80: backgroundView=<_UITextFieldRoundedRectBackgroundViewNeue: 0x127d0e710; frame = (0 0; 220 34); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600000f8ec60>>, fillColor=<UIDynamicModifiedColor: 0x6000001874e0; contrast = normal, baseColor = <UIDynamicSystemColor: 0x600001a9bb40; name = systemRedColor>>, textfield=<UITextField 0x127d0bf90>>; layer = <CALayer: 0x600000fe3ee0>>"
)

4.Target-Action

  • 普通写法
[self.button addTarget:self action:@selector(Click:) forControlEvents:UIControlEventTouchUpInside];

- (void)Click:(UIButton *)sender {
    NSLog(@"按钮被点击");
}
  • RAC
[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
    NSLog(@"%@",x);
}];


<UIButton: 0x15a50d9f0; frame = (101 240; 213 205); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60000262fb60>>

5.UITextField 输入文本UITextField的文本监听

  • RAC 写法:
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
    NSLog(@"%@",x);
}];
当在self.textField输入文字h时,会实时打印出变化后的文字
2019-05-16 18:02:58.562309+0800 001---RAC[54530:4536864] h
2019-05-16 18:02:59.049225+0800 001---RAC[54530:4536864] hh
2019-05-16 18:02:59.288995+0800 001---RAC[54530:4536864] hhh

6.手势

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
self.label.userInteractionEnabled = YES;
[self.label addGestureRecognizer:tap];
[tap.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
    NSLog(@"%@",x);
}];

7、数组和字典的遍历

  • 数组
TestModel *model1 = [[TestModel alloc] init];
model1.age = 1;
model1.height = 11;

TestModel *model2 = [[TestModel alloc] init];
model2.age = 2;
model2.height = 22;


TestModel *model3 = [[TestModel alloc] init];
model3.age = 3;
model3.height = 33;

NSArray *array = @[model1,model2,model3];
[array.rac_sequence.signal subscribeNext:^(TestModel * _Nullable x) {
    NSLog(@"%d",x.age);
}];
  • 字典
NSDictionary *dict = @{@"name":@"凡几多",@"age":@"20",@"sex":@"男"};
[dict.rac_sequence.signal subscribeNext:^(id  _Nullable x) {
    //元祖
    NSLog(@"%@",x);
    RACTwoTuple *tuple = (RACTwoTuple *)x;
    NSLog(@"key == %@ , value = %@",tuple[0],tuple[1]);
}];

8、计时器

  • NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:30.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; // 需要加入手动RunLoop,需要注意的是在NSTimer工作期间self是被强引用的
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; // 使用NSRunLoopCommonModes才能保证RunLoop切换模式时,NSTimer能正常工作。

停止计时器
- (void)stopTimer {
    if (_timer) {
        [_timer invalidate];
    }
}
  • RAC
- 主线程
[[RACSignal interval:2.0 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(NSDate * _Nullable x) {
    NSLog(@"%@",x);
    NSLog(@"%@",[NSThread currentThread]);

}];

- 子线程
[[RACSignal interval:2.0 onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh name:@"abc"]] subscribeNext:^(NSDate * _Nullable x) {
    NSLog(@"%@",[NSThread currentThread]);
}];

拓展

  • 数组便利是先执行Block 里面的在执行 block 外面的语句 参考文章 利用栅栏函数完成任务的调度
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    TestModel *model1 = [[TestModel alloc] init];
    model1.age = 1;
    model1.height = 11;
    
    TestModel *model2 = [[TestModel alloc] init];
    model2.age = 2;
    model2.height = 22;
    
    
    TestModel *model3 = [[TestModel alloc] init];
    model3.age = 3;
    model3.height = 33;
    
    NSArray *array = @[model1,model2,model3];
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"🔞 START: %@", [NSThread currentThread]);
    dispatch_async(concurrentQueue, ^{
        [array.rac_sequence.signal subscribeNext:^(TestModel * _Nullable x) {
            NSLog(@"%@",[NSThread currentThread]);
            NSLog(@"%d",x.age);
        }];
    }); // ⬅️ 任务一
    dispatch_barrier_async(concurrentQueue, ^{ sleep(3); NSLog(@"🚥🚥 %@", [NSThread currentThread]);}); // ⬅️ Barrie 任务
    dispatch_async(concurrentQueue, ^{ NSLog(@"111");}); // ⬅️ 任务四
    NSLog(@"🔞 END: %@", [NSThread currentThread]);
    
}

RAC高阶函数案例

  • 1.信号映射:map与flattenMap
  • 2.信号过滤:filter、ignore、distinctUntilChanged
  • 3.信号合并:combintLatest、reduce、merge、zipWidth
  • 4.信号连接:concat、then
  • 5.信号操作时间:timeout、interval、dely
  • 6.信号取值:take、takeLast、takeUntil
  • 7.信号跳过:skip
  • 8.信号发送顺序:donext、cocompleted
  • 9.获取信号中的信号:switchToLatest
  • 10.信号错误重试:retry

RAC高阶函数使用案例和场景

信号映射 flattenMap的使用
  • 1.flattenMap的场景:比如我输入一个号码,需要在前面+86.
    // 映射
    // 映射它的本质就是一种对应关系。
    [[self.textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        NSLog(@"textField text is  %@",value); // 输入的value值
        return  [RACSignal return:[NSString stringWithFormat:@"+86%@",value]]; // 通过拼接 返回值 传递到x里面
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"result is : %@",x); // 最终结果
    }];

image.png

信号过滤 filter的使用
  • filter的场景:比如进行一个昵称长度进行比较。
    // 过滤
    [[self.textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
        if (self.textField.text.length > 6) {
            self.textField.text = [self.textField.text substringToIndex:6];
            self.label.text = @"太长了";
            // 如果字符串大于6个 将信息显示到 标签label 里面进行提示
        }
        return  value.length < 6; // 如果字符没有大于6个 正常操作
    }] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"过滤 --- %@",x); // 如果大于6个的话 这里不会执行
    }];

image.png

image.png

信号合并(组合内聚) combineLatestWith,combineLatest的使用
  • combineLatestWith、combineLatest的场景 : 比如进行一个表格提交。例如注册账号。输入的内容有 姓名,年龄,地区,性别,简介等输入信息进行注册。那么就需要用到组合。将所有关联的信号进行组合。然后进行聚合输出一个结果。提交到服务端

  • combineLatestWith 组合信号

    RACSignal *sigalA = self.textField.rac_textSignal;
    RACSignal *sigalB = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
    // 组合
    RACSignal *comSig = [sigalA combineLatestWith:sigalB];
    [comSig subscribeNext:^(id  _Nullable x) {
        NSLog(@"组合 %@",x);
    }];
  • combineLatest 组合聚合

 /**
     组合内聚
        1.先组合
        2.再聚合
     */
    
    RACSignal *sigalA = self.nameTextField.rac_textSignal;
    RACSignal *sigalA1 = self.sexTextField.rac_textSignal;
    RACSignal *sigalA2 = self.ageTextField.rac_textSignal;
    RACSignal *sigalB = [self.saveBtn rac_signalForControlEvents:UIControlEventTouchUpInside];
    
    [[RACSignal combineLatest:@[sigalA,sigalA1,sigalA2,sigalB] reduce:^id _Nullable(id valueA,id valueA1,id valueA2,id valueB){
        // reduce 需要返回信号的值
        // 也可以进行判断进行返回

        return [NSString stringWithFormat:@"聚合 --- valueA is %@  valueA1 is %@ valueA2 is %@  valueB is %@",valueA,valueA1,valueA2,valueB];

        }]subscribeNext:^(id  _Nullable x) {

                NSLog(@"聚合 ---  x is %@",x); // 最终逻辑

    }];

image.png

  • 打印了三遍 image.png
信号连接 concat
  • 信号相连,依次订阅到。要注意:前面的信号必须发送完成,才能接力到下一个信号。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"one"];
    // [subscriber sendError:nil];
    // 前面的信号必须发送完成,才能接力到下一个信号。
    [subscriber sendCompleted];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signalA dispose");
    }];
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"two"];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signalB dispose");
    }];
}];
[[signalA concat:signalB] subscribeNext:^(id  _Nullable x) {
    NSLog(@"concat:%@", x);
}];

image.png

信号操作时间

timeout

**

NSLog(@"%s", __func__);
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signal dispose");
    }];
}] timeout:5 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id  _Nullable x) {
    NSLog(@"timeOut:%@", x);
} error:^(NSError * _Nullable error) {
    NSLog(@"timeOut-error:%@", error);
}];

输出:
20:09:29 -[ViewController testTimeOut]
20:09:29 signal dispose
20:09:34 timeOut-error:Error Domain=RACSignalErrorDomain Code=1 "(null)"

interval

[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(NSDate * _Nullable x) {
    NSLog(@"interval:%@",[NSThread currentThread]);
}];

dely

[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"1"];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signal dispose");
    }];
}] delay:3] subscribeNext:^(id  _Nullable x) {
    NSLog(@"dely:%@", x);
}];

信号取值

take

屏蔽一些信号,取前 N 个信号。

[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"1"];
    [subscriber sendNext:@2];
    [subscriber sendNext:@3];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signal dispose");
    }];
}] take:2] subscribeNext:^(id  _Nullable x) {
    NSLog(@"take:%@", x);
}];

tekeLast

屏蔽一些信号,取后 N 个信号。注意:必须发送完成信号,才能确定最后的 N 个信号。

**

[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"1"];
    [subscriber sendNext:@2];
    [subscriber sendNext:@3];
    // 必须发送完成信号,才能确定最后的 N 个信号。
    [subscriber sendCompleted];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signal dispose");
    }];
}] takeLast:2] subscribeNext:^(id  _Nullable x) {
    NSLog(@"takeLast:%@", x);
}];

takeUntil

[A takeUntil:B]:B发出信号或者发出完成信号后,A的信号也就订阅不到了。

**

RACSubject *subjectA = [RACSubject subject];
RACSubject *subjectB = [RACSubject subject];
[[subjectA takeUntil:subjectB] subscribeNext:^(id  _Nullable x) {
    NSLog(@"takeUntil:%@", x);
}];
[subjectA sendNext:@"a-1"];
[subjectA sendNext:@"a-2"];
// [subjectB sendNext:@"b-2"];
[subjectA sendNext:@"a-3"];

[subjectA sendNext:@"a-4"];
[subjectB sendNext:@"b-1"];
[subjectA sendNext:@"a-5"];

信号跳跃

skip

跳过前 N 个信号。

**

[[self.tf.rac_textSignal skip:3] subscribeNext:^(NSString * _Nullable x) {
    NSLog(@"skip:%@", x);
}];

信号发送顺序

doNext

在源信号发送之前,可以在doNextBlock中做一些附加操作。

**

RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signalB-1"];
    [subscriber sendNext:@"signalB-2"];
    [subscriber sendNext:@"signalB-3"];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signalB dispose");
    }];
}];
[[signalB doNext:^(id  _Nullable x) {
    NSLog(@"do:%@", x);
}] subscribeNext:^(id  _Nullable x) {
    NSLog(@"订阅:%@", x);
}];

cocompleted

在源信号发送完成之前,可以在doCompletedBlock中做一些附加操作。 **

RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signalB-1"];
    [subscriber sendNext:@"signalB-2"];
    [subscriber sendCompleted];
    [subscriber sendNext:@"signalB-3"];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signalB dispose");
    }];
}];
[[signalB doCompleted:^{
    NSLog(@"do someting befor completed");
}] subscribeNext:^(id  _Nullable x) {
    NSLog(@"订阅到:%@", x);
}];

获取信号中的信号

switchToLatest

  • 获取信号中信号最近发出信号,订阅最近发出的信号。
RACSubject *signal = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];
[[signalOfSignals switchToLatest] subscribeNext:^(id  _Nullable x) {
    NSLog(@"switchToLatest:%@", x);
}];
// 注意switchToLatest:只能用于信号中的信号
[signalOfSignals sendNext:signal];
[signal sendNext:@"i am a signal"];

信号错误重试

retry

发送失败就会重新执行创建信号时的block,直至成功发送。

static int i = 0;
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    if (i == 5) {
        [subscriber sendNext:[NSString stringWithFormat:@"%d", i]];
    } else {
        [subscriber sendError:nil];
    }
    i++;
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signal dispose");
    }];
}] retry] subscribeNext:^(id  _Nullable x) {
    NSLog(@"next:%@", x);
} error:^(NSError * _Nullable error) {
    NSLog(@"error:%@", error);
}];

replay

被 N 次订阅,则发送 N 遍信号。

RACSignal *signalA = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signalA-1"];
    [subscriber sendNext:@"signalA-2"];
    [subscriber sendNext:@"signalA-3"];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signalA dispose");
    }];
}] replay];
[signalA subscribeNext:^(id  _Nullable x) {
    NSLog(@"replay-1:%@", x);
}];
[signalA subscribeNext:^(id  _Nullable x) {
    NSLog(@"replay-2:%@", x);
}];