ReactiveCocoa详解

一、RAC介绍

RAC 是一个 iOS 中的函数式响应式编程框架,一般与MVVM配套使用。
在非RAC开发中,都是习惯赋值。在RAC开发中,需要改变开发思维,由赋值转变为绑定,并不需要重写set方法。
RAC项目的Podfile如下:

use_frameworks!
target '工程名称' do
pod 'ReactiveObjC', '~> 3.0.0'
end

如果使用Swift作为开发语言,建议使用RXSwift。关于RXSwift,以后会单独写一篇文章介绍。

RAC使用注意事项:
1.RAC学习曲线陡峭,团队开发时要谨慎使用,确保项目组所有成员都会RAC,再使用RAC构建项目。如果一个人开发一个项目,要使用RAC,先跟项目经理商量一下,不要自己想当然的想用就用,这样如果公司要加人,会增加招人成本,公司不一定乐意。
2.在使用过程中,尽量保持同一个项目的成员,代码风格一致,这样方便管理维护。
3.关于学习曲线陡峭,有的同学可能不以为然,随便写个demo,体验了几个RAC基本操作方法,就觉得自己已经会用RAC了。其实不然,要真正的用RAC构造整个项目,再配上MVVM,是需要一个较长学习周期的。

二、RAC常见用法

  • 代替代理
    rac_signalForSelector:用于替代代理
    // 需求:自定义redView,监听红色view中按钮点击
    // 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
    // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
    // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
    [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
    }];
  • 代替KVO
    rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
    // observer:可以传入nil
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {

        NSLog(@"%@",x);

    }];
  • 监听事件 (代替addTarget)
    rac_signalForControlEvents:用于监听某个事件。
[[self.btn rac_signalForControlEvents:(UIControlEventTouchUpInside)]subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"%@",x);
    }];
  • 代替通知
    rac_addObserverForName:用于监听某个通知
// 把监听到的通知转换信号
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];
  • 监听文本框文字改变
    rac_textSignal:只要文本框发出改变就会发出这个信号
[_textField.rac_textSignal subscribeNext:^(id x) {
       NSLog(@"文字改变了%@",x);
   }];

三、常见类解释

RactiveCocoa中很重要的两个class,一个是RACSignal,一个是RACSequence,而这两个class的super class就是RACStream。
RACStream: 表示一个基本单元可以为任意值,其值会随着事件的变化而变化。可以在其上进行一些复杂的操作运算(map,filter,skip,take等.)此类不会被经常使用, 多情况下表现为signal和sequences(RACSignal 和RACSequence继承于RACStream类)。
RACSiganl:只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。signal能发送3种不同类型的事件:Next,Completed,Error。
信号三部曲:创建信号、订阅信号、发送信号。

// 1.创建信号
    RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 2.发送信号,必须是一个对象类型
        [subscriber sendNext:@1];
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
        //return nil;
        return [RACDisposable disposableWithBlock:^{
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            // 执行完Block后,当前信号就不在被订阅了。
            NSLog(@"信号被销毁");
        }];
    }];

    // 3.订阅信号,才会激活信号.
    [siganl subscribeNext:^(id x) {
        // block调用时刻:每当有信号发出数据,就会调用block.
        NSLog(@"接收到数据:%@",x);
    }];

RACSubject: 信号提供者,自己可以充当信号,又能发送信号。

// 1.创建信号
    RACSubject *subject = [RACSubject subject];
    // 2.订阅信号
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用.
        NSLog(@"第一个订阅者%@",x);
    }];
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用.
        NSLog(@"第二个订阅者%@",x);
    }];
    // 3.发送信号
    [subject sendNext:@"1"];

RACReplaySubject:重复提供信号类,RACSubject的子类。
RACDisposable:用于取消订阅或者清理资源,在一个completed或者error事件之后,就会自动触发它,订阅会自动移除。也可以通过RACDisposable 手动移除订阅。
RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。

- (RACSignal *)bindTextField:(UITextField *)textField slider:(UISlider *)slider
{
    //通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
    RACChannelTerminal *sliderChanel = [slider rac_newValueChannelWithNilValue:nil];
    RACChannelTerminal *textFieldChanel = [textField rac_newTextChannel];
    
    //双向绑定
    [[textFieldChanel map:^id _Nullable(id  _Nullable value) {
        return @([value floatValue]);
    }] subscribe:sliderChanel]; //输入框的值流向滑杆
    [[sliderChanel map:^id _Nullable(id  _Nullable value) {
        return [NSString stringWithFormat:@"%.2f", [value floatValue]];
    }] subscribe:textFieldChanel];  //滑杆的值流向输入框
    
    //merge合并信号,任何一个信号发送数据,都能监听到。
    return [textFieldChanel merge:sliderChanel];
}

//-----------------------------
//功能实现:绑定滑杆和输入框,滑杆滑动输入框的内容跟随变化,输入框值改变,滑杆自动滑动到对应值位置。当三个滑杆或者输入框的值均改变过一次时,colorView的颜色作出改变。
- (void)demo1
{
    _redInput.text = _greenInput.text = _blueInput.text = @"0.5";
    
    RACSignal *redSignal = [self bindTextField:_redInput slider:_redSlider];
    RACSignal *greenSignal = [self bindTextField:_greenInput slider:_greeSlider];
    RACSignal *blueSignal = [self bindTextField:_blueInput slider:_blueSlider];

    //将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
    RAC(_colorView, backgroundColor) = [[RACSignal combineLatest:@[redSignal, greenSignal, blueSignal]] map:^id _Nullable(RACTuple * _Nullable value) {
        return [UIColor colorWithRed:[value[0] floatValue]
                               green:[value[1] floatValue]
                                blue:[value[2] floatValue]
                               alpha:1];
    }];
}

RACTuple:元组类,类似NSArray,用来包装值.
RACSequence:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典,达到对值的一些过滤和转换。

// 遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
    NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
        // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
        RACTupleUnpack(NSString *key,NSString *value) = x;
        // 相当于以下写法
//        NSString *key = x[0];
//        NSString *value = x[1];
        NSLog(@"%@ %@",key,value);
    }];

RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。

// 一、RACCommand使用步骤:
    // 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
    // 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
    // 3.执行命令 - (RACSignal *)execute:(id)input
    
    // 二、RACCommand使用注意:
    // 1.signalBlock必须要返回一个信号,不能传nil.
    // 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
    // 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
    // 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
    
    // 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
    // 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
    // 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
    
    // 四、如何拿到RACCommand中返回信号发出的数据。
    // 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
    // 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
    
    // 五、监听当前命令是否正在执行executing
    
    // 六、使用场景,监听按钮点击,网络请求
// 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        NSLog(@"执行命令");
        // 创建空信号,必须返回信号
        //        return [RACSignal empty];
        // 2.创建信号,用来传递数据
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"请求数据"];
            // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
            [subscriber sendCompleted];
            return nil;
        }];
    }];

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

// 3.订阅RACCommand中的信号
    [command.executionSignals subscribeNext:^(id x) {
        //订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
        [x subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
    }];

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

// 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
    [[command.executing skip:1] subscribeNext:^(id x) {
       // executing:判断当前的block是否在执行,执行完之后会返回NO
        if ([x boolValue] == YES) {
            // 正在执行
            NSLog(@"正在执行");
        }else{
            // 执行完成
            NSLog(@"执行完成");
        }
    }];

   // 5.执行命令(控制器里执行此句代码)
    [self.login_vm.loginCommand execute:nil];

RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建。
RACScheduler:RAC中的队列,用GCD封装的。
RACUnit :表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.

四、RAC常用宏

1、RAC 绑定一个信号
RAC宏允许直接把信号的输出应用到对象的属性上 每次信号产生一个next事件,传递过来的值都会应用到该属性上

//1、RAC 把一个对象的摸个属性绑定一个信号,只有发出信号,就会吧信号的内容给对象的属性赋值。
    //这里吧label的text属性绑定到textField改变信号中,textfield的内容发生改变的时候就会发出信号,只要文本框文字改变,就会修改label的文字。
    RAC(self.label,text) = _textfield.rac_textSignal;

2、RACObserve 相当于kvo使用

//person为一个模型,只要模型里面的name字段改变,就会把最新的name值显示在label上
[RACObserve(self.person, name) subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
        self.nameLabel.text = x;
    }];

3、@weakify 和@strongify 解决循环引用,注意这两个宏是配套使用才有效。

@weakify(self);
    [[self.testTextFileld rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@",x);
        @strongify(self);
        self.label.text = x;
    }];

4、 RACChannelTo 用于双向绑定
RACChannelTerminal也可以实现双向绑定,例子上面已写

RACChannelTo(view, property) = RACChannelTo(model, property);

5、RACTuplePack:把数据包装成RACTuple(元组类)

// 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@(1),@(30));

6、RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。

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

五、常见操作方法

所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,因此只要继承RACStream就有了操作处理方法。

1. bind(绑定) 核心方法

ReactiveCocoa 操作的核心方法是 bind(绑定),而且也是RAC中核心开发方式。之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
RAC底层都是调用bind, 在开发中很少直接使用 bind 方法,bind属于RAC中的底层方法,我们只需要调用封装好的方法,bind用作了解即可.

2. flattenMap

把源信号的内容映射成一个新的信号,信号可以是任意类型
使用步骤:
1.传入一个block,block类型是返回值RACStream,参数value
2.参数value就是源信号的内容,拿到源信号的内容做处理
3.包装成RACReturnSignal信号,返回出去

//监听文本框的内容改变,重新映射成一个新的信号
[[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
    // block调用时机:信号源发出的时候
    // block作用:改变信号的内容
    // 返回RACReturnSignal (需要导入RACReturnSignal.h)
    return [RACReturnSignal return:[NSString stringWithFormat:@"信号内容:%@", value]];

}] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

3. Map

把源信号的值映射成一个新的值
如果map的是一个sequence,那么会遍历里面的么一个元素,遍历的每一个值,都会做相同的处理

//监听文本框的内容改变,映射成一个新值.
[[_textField.rac_textSignal map:^id(id value) {
   //把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
    return [NSString stringWithFormat:@"信号内容: %@", value];
    
}] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

FlatternMap 和 Map 的区别

FlatternMap 中的Block 返回信号。
Map 中的Block 返回对象。
开发中,如果信号发出的值 不是信号 ,映射一般使用 Map
如果信号发出的值 是信号,映射一般使用 FlatternMap。

信号中的信号(signalOfsignals)用flatternMap:

// 创建信号中的信号
RACSubject *signalOfsignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];

[[signalOfsignals flattenMap:^RACStream *(id value) {
 // 当signalOfsignals的signals发出信号才会调用
    return value;

}] subscribeNext:^(id x) {
    // 只有signalOfsignals的signal发出信号才会调用,因为内部订阅了bindBlock中返回的信号,也就是flattenMap返回的信号。
    // 也就是flattenMap返回的信号发出内容,才会调用。
    NSLog(@"signalOfsignals:%@",x);
}];

// 信号的信号发送信号
[signalOfsignals sendNext:signal];

// 信号发送内容
[signal sendNext:@"hi"];

------- 组合 -------

组合就是将多个信号按照某种规则进行拼接,合成新的信号。

4. concat

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

//拼接信号 signalA、 signalB、 signalC

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"Hello"];
    [subscriber sendCompleted];
    return nil;
}];

RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"World"];
    [subscriber sendCompleted];
    return nil;
}];

RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"!"];
    [subscriber sendCompleted];
    return nil;
}];

// 拼接 A B, 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活。
RACSignal *concatSignalAB = [signalA concat:signalB];

// A B + C
RACSignal *concatSignalABC = [concatSignalAB concat:signalC];

// 订阅拼接的信号, 内部会按顺序订阅 A->B->C
// 注意:第一个信号必须发送完成,第二个信号才会被激活...
[concatSignalABC subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

5. then

用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。
注意使用then,之前信号的值会被忽略掉

6. merge

合并信号,任何一个信号发送数据,都能监听到。
合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
合并信号一被订阅,就会订阅里面所有的信号。
只要有一个信号被发出就会被监听。

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
      [subscriber sendNext:@"A"];
      return nil;
  }];

  RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
      [subscriber sendNext:@"B"];
      return nil;
  }];

  // 合并信号, 任何一个信号发送数据,都能监听到
  RACSignal *mergeSianl = [signalA merge:signalB];

  [mergeSianl subscribeNext:^(id x) {
      NSLog(@"%@", x);
  }];
//输出
//A
//B

7. combineLatest

将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
必须两个信号都发出内容,才会被触发, 并且把两个信号的 最后一次 发送的值组合成元组发出。

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"A1"];
        [subscriber sendNext:@"A2"];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"B1"];
        [subscriber sendNext:@"B2"];
        [subscriber sendNext:@"B3"];
        return nil;
    }];
    
    RACSignal *combineSianal = [signalA combineLatestWith:signalB];
    
    [combineSianal subscribeNext:^(id x) {
        NSLog(@"combineLatest:%@", x);
    }];
/*
输出
2018-02-01 17:39:04.914804+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60400000fc70> (
    A2,
    B1
)
2018-02-01 17:39:04.915092+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000faa0> (
    A2,
    B2
)
2018-02-01 17:39:04.915270+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000fff0> (
    A2,
    B3
)
*/

//如果用[signalB combineLatestWith:signalA],那么结果就是(B3, A1)  (B3, A2)

8. reduce

把信号发出元组的值聚合成一个值
reduce 合并信号的数据,进行汇总计算使用
订阅聚合信号,每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值。
常见的用法,(先组合在聚合)一般跟combineLatest 一起使用

//判断用户名和密码同时存在的时候,按钮才能点击
[[RACSignal combineLatest:@[self.nameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id _Nullable(NSString *name, NSString *pwd){
        return @(name.length > 0 && pwd.length > 0);
    }] subscribeNext:^(id  _Nullable x) {
        _btn.enabled = [x boolValue];
    }];

9. zip

把两个信号压缩成一个信号,只有当两个信号 同时 发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
1. 定义压缩信号,内部就会自动订阅signalA,signalB
2. 每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把每个信号 第一次 发出的值包装成元组发出
注意:combineLatest与zip用法相似,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"A1"];
        [subscriber sendNext:@"A2"];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"B1"];
        [subscriber sendNext:@"B2"];
        [subscriber sendNext:@"B3"];
        return nil;
    }];
    
    RACSignal *zipSignal = [signalA zipWith:signalB];
    
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
/*
结果:
2018-02-01 18:00:04.585460+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x604000005840> (
    A1,
    B1
)
2018-02-01 18:00:04.585790+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x6040000058d0> (
    A2,
    B2
)
*/

10. filter

过滤信号,使用它可以获取满足条件的信号

// 每次信号发出,会先执行过滤条件判断.
[[_textField.rac_textSignal filter:^BOOL(NSString *value) {
    NSLog(@"原信号: %@", value);
    // 过滤 长度 <= 3 的信号
    return value.length > 3;
}] subscribeNext:^(id x) {
    NSLog(@"长度大于3的信号:%@", x);
}];

11. ignore

忽略某些信号
底层调用了 filter 与 过滤值进行比较,若相等返回则 NO

//过滤掉值为111的信号
[[_textField.rac_textSignal ignore:@"111"] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

12. distinctUntilChanged

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

[[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
      NSLog(@"%@",x);
  }];

13. skip

跳过第N次的发送的信号。

// 表示输入第一次,不会被监听到,跳过第一次发出的信号
[[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
 NSLog(@"%@",x);
}];

14. take

取前N次的发送的信号。

RACSubject *subject = [RACSubject subject] ;
  // 取 前两次 发送的信号
  [[subject take:2] subscribeNext:^(id x) {
      NSLog(@"%@", x);
  }];
  [subject sendNext:@1];
  [subject sendNext:@2];
  [subject sendNext:@3];

  // 输出
  2017-01-03 17:35:54.566 ReactiveCocoa进阶[4969:1677908] 1
  2017-01-03 17:35:54.567 ReactiveCocoa进阶[4969:1677908] 2

15. takeLast

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

RACSubject *subject = [RACSubject subject] ;
  // 取 后两次 发送的信号
  [[subject takeLast:2] subscribeNext:^(id x) {
      NSLog(@"%@", x);
  }];
  
  [subject sendNext:@1];
  [subject sendNext:@2];
  [subject sendNext:@3];
  // 必须 跳用完成
  [subject sendCompleted];

16. takeUntil

获取信号直到某个信号执行完成

// 监听文本框的改变直到当前对象被销毁
[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];

17. switchToLatest

用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。
switchToLatest:只能用于信号中的信号

RACSubject *signalOfSignals = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    
    // 获取信号中信号最近发出信号,订阅最近发出的信号。
    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    
    [signalOfSignals sendNext:signal];
    [signal sendNext:@1];

---- 秩序 ----

秩序包括 doNext 和 doCompleted 这两个方法,主要是在 执行sendNext 或者 sendCompleted 之前,先执行这些方法中Block。

18. doNext

执行sendNext之前,会先执行这个doNext的 Block

19. doCompleted

执行sendCompleted之前,会先执行这doCompletedBlock

[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"hi"];
        [subscriber sendCompleted];
        return nil;
        
    }] doNext:^(id x) {
        // 执行 [subscriber sendNext:@"hi"] 之前会调用这个 Block
        NSLog(@"doNext");
        
    }] doCompleted:^{
        // 执行 [subscriber sendCompleted] 之前会调用这 Block
        NSLog(@"doCompleted");
        
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];

/*
运行结果:
2018-02-02 09:56:39.206710+0800 RAC 101[40487:30848792] doNext
2018-02-02 09:56:39.206954+0800 RAC 101[40487:30848792] hi
2018-02-02 09:56:39.207112+0800 RAC 101[40487:30848792] doCompleted
*/

---- 线程 ----

ReactiveCocoa 中的线程操作 包括 deliverOnsubscribeOn这两种,将 传递的内容 或 创建信号时 block中的代码 切换到指定的线程中执行。

20. deliverOn

内容传递切换到指定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。

// 在子线程中执行
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"%@", [NSThread currentThread]);
            [subscriber sendNext:@123];
            [subscriber sendCompleted];
            return nil;
        }]
         deliverOn:[RACScheduler mainThreadScheduler]]
         subscribeNext:^(id x) {
             NSLog(@"%@", x);
             NSLog(@"%@", [NSThread currentThread]);
         }];
    });
/*
运行结果:
2018-02-02 14:10:28.938587+0800 RAC 101[45182:31032478] <NSThread: 0x604000279c00>{number = 3, name = (null)}
2018-02-02 14:10:29.076247+0800 RAC 101[45182:31031131] 123
2018-02-02 14:10:29.076494+0800 RAC 101[45182:31031131] <NSThread: 0x60400006f540>{number = 1, name = main}
*/

可以看到 副作用 在 子线程 中执行,而 传递的内容 在 主线程 中接收

21. subscribeOn

subscribeOn则是将 内容传递 和 副作用 都会切换到指定线程中。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"%@", [NSThread currentThread]);
            [subscriber sendNext:@123];
            [subscriber sendCompleted];
            return nil;
        }]
         subscribeOn:[RACScheduler mainThreadScheduler]] //传递的内容到主线程中
         subscribeNext:^(id x) {
             NSLog(@"%@", x);
             NSLog(@"%@", [NSThread currentThread]);
         }];
    });
/*
运行结果:
2018-02-02 14:15:23.956524+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
2018-02-02 14:15:23.956737+0800 RAC 101[45319:31066198] 123
2018-02-02 14:15:23.956961+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
*/

可以看到,内容传递 和 副作用 都切换到了 主线程 执行

----时间----

时间操作就会设置信号超时,定时和延时。

22. interval

定时:每隔一段时间发出信号

// 每隔1秒发送信号,指定当前线程执行
    [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
        NSLog(@"定时:%@", x);
    }];

23. timeout

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

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 不发送信号,模拟超时状态
        // [subscriber sendNext:@"hello"];
        //[subscriber sendCompleted];
        return nil;
    }] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 设置1秒超时
    
    [signal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    } error:^(NSError *error) {
        NSLog(@"%@", error);
    }];
    
    // 执行代码 1秒后 输出:
    2017-01-04 13:48:55.195 ReactiveCocoa进阶[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)"

24. retry

重试:只要 发送错误 sendError: 就会重新执行创建信号的Block直到成功

[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        if (i == 3) {
            [subscriber sendNext:@"Hello"];
        } else {
            // 发送错误
            NSLog(@"收到错误:%d", i);
            [subscriber sendError:nil];
        }
        i++;
        return nil;
    }] retry] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    } error:^(NSError *error) {
        NSLog(@"%@", error);
    }];
// 输出
2017-01-04 14:36:51.594 ReactiveCocoa进阶[2443:667226] 收到错误信息:0
2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:1
2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:2
2017-01-04 14:36:51.596 ReactiveCocoa进阶[2443:667226] Hello

25. replay

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

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        [subscriber sendNext:@2];
        return nil;
    }] replay];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
/*
运行结果:
2018-02-02 14:37:45.269758+0800 RAC 101[45938:31201667] 1
2018-02-02 14:37:45.270018+0800 RAC 101[45938:31201667] 2
2018-02-02 14:37:45.270175+0800 RAC 101[45938:31201667] 1
2018-02-02 14:37:45.270300+0800 RAC 101[45938:31201667] 2
/*

26. throttle

节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

RACSubject *subject = [RACSubject subject];
    // 节流1秒,1秒后接收最后一个发送的信号
    [[subject throttle:1] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    
    // 输出
    2018-02-02 14:53:00.234975+0800 RAC 101[46278:31282576] 3

扩展:利用RAC封装网络请求

通过RAC,我们可以奖网络请求封装为信号的形式,数据的获取可以通过订阅信号的方式来得到。与传统的网络请求相比,是不是感觉耳目一新?

#import <AFNetworking/AFNetworking.h>
#import "AFNetworking.h"
#import <ReactiveObjC.h>

@interface NetworkingManager : AFHTTPSessionManager
+ (instancetype)shareManager;
- (RACSignal *)GET:(NSString *)url parameters:(id)parameters;
@end

--------------------------------------------------------------------
@implementation NetworkingManager

+ (instancetype)shareManager
{
    static NetworkingManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc]init];
        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html" ,nil];
    });
    return manager;
}

//GET请求
- (RACSignal *)GET:(NSString *)url parameters:(id)parameters
{
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [self GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            [subscriber sendNext:responseObject];
            [subscriber sendCompleted];
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            [subscriber sendError:error];
        }];
        return nil;
    }];
}
@end

--------------------------------------------------------------------
//请求网络数据
- (void)fetchWeatherData
{
    NetworkingManager *manager = [NetworkingManager shareManager];
    [[manager GET:@"http://www.weather.com.cn/data/sk/101010100.html" parameters:nil] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"%@", error.localizedDescription);
    }];
}
//如上:调用请求,订阅信号后得到的x就是需要的json数据。

参考文章:ReactiveCocoa进阶最快让你上手ReactiveCocoa之基础篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容

  • RAC使用测试Demo下载:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees阅读 6,372评论 3 10
  • 前言由于时间的问题,暂且只更新这么多了,后续还会持续更新本文《最快让你上手ReactiveCocoa之进阶篇》,目...
    Karos_凯阅读 1,739评论 0 6
  • 1.ReactiveCocoa常见操作方法介绍。 1.1 ReactiveCocoa操作须知 所有的信号(RACS...
    萌芽的冬天阅读 1,023评论 0 5
  • 偷偷的,慢慢的,静静的听雨,看雨,在窗外,在窗前。把身体向窗外移动,把头伸出窗外,清新的晓晨,清新的空气,缠...
    时间工匠传媒阅读 372评论 0 2
  • 平常的日子里,我祈求一段令人销魂的风景:一首平静如水的音乐:一个没有距离的心约! 出租小屋,背东朝西,闲暇时分,总...
    上善若水_cd86阅读 398评论 23 51