iOS ReacttiveObjc

2018-05-01  本文已影响267人  Never_Yg

Block

block类型:

  //block: 就是找到block装起来的代码去执行
    void(^block)(void) = ^{
        NSLog(@"block封装的代码");
    };
    // 这个block的类型是 void(^)()
    block();

使用

- (void)masonry
{
    self.test(); //这里执行其实是执行block里面装的代码
}

//block做返回值
- (void(^)(void))test
{
    void (^block)(void) = ^{
        NSLog(@"执行这里的代码");
    };
    return block;
}

MVVM

目的: 将UI逻辑与业务逻辑分离
注意点:

问题: 如果view引用了ViewModel, 但ViewModel没有引用View, 那么ViewModel如何更新视图呢?

响应式编程思想(KVO)

链式编程思想(block做参数, block返回对象本身)

ReactiveCocoa主要由以下四大核心组件构成

万物皆信号

RACSiganl

[requestSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"next的block");
    } error:^(NSError * _Nullable error) {
        NSLog(@"error的block");
    } completed:^{
        NSLog(@"completed的block");
    }];

一个信号最好只订阅一次, 这样就没有反复创建订阅者的副作用

// RACSignal使用步骤:
    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
    // 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 3.发送信号 - (void)sendNext:(id)value
    
    
    // RACSignal底层实现:
    // 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
    // 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
    // 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
    // 2.1 subscribeNext内部会调用siganl的didSubscribe
    // 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
    // 3.1 sendNext底层其实就是执行subscriber的nextBlock
    
    // 1.创建信号
    RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        
        // 2.发送信号
        [subscriber sendNext:@1];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
 
        return [RACDisposable disposableWithBlock:^{
            
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            
            // 执行完Block后,当前信号就不在被订阅了。
            
            NSLog(@"信号被销毁");
            
        }];
    }];
    
    // 3.订阅信号,才会激活信号.
    [siganl subscribeNext:^(id x) {
        // block调用时刻:每当有信号发出数据,就会调用block.
        NSLog(@"接收到数据:%@",x);
    }];

RACSubscriber(订阅者), 用于发送信号, 这是一个协议不是一个类
RACDisposable用于取消订阅或者清理资源, 当信号发送完成或发送错误的时候, 就会自动触发它
RACSubject: 遵循了RACsubscriber协议的RACSignal,所以自己是信号, 也可以订阅其他信号
RACReplaySubject: RACSubject的子类, 重复提供信号, 先把发送过的信号缓存起来, 再次订阅时直接发送缓存的值

解释: 信号类RACSignal和RACSubject都遵守先订阅再发送信号原则, 但是RACReplaySubject可以先发送信号(内部是一个数组把信号保存起来), 再订阅, 因为它会把信号缓存起来
使用场景: 一个类要拿到多个值的时候

// RACSubject使用步骤
    // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
    // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 3.发送信号 sendNext:(id)value

    // RACSubject:底层实现和RACSignal不一样。
    // 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
    // 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。

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

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

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


    // RACReplaySubject使用步骤:
    // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
    // 2.可以先订阅信号,也可以先发送信号。
    // 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 2.2 发送信号 sendNext:(id)value

    // RACReplaySubject:底层实现和RACSubject不一样。
    // 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
    // 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock

    // 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
    // 也就是先保存值,在订阅值。

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

    // 2.发送信号
    [replaySubject sendNext:@1];
    [replaySubject sendNext:@2];

    // 3.订阅信号
    [replaySubject subscribeNext:^(id x) {

        NSLog(@"第一个订阅者接收到的数据%@",x);
    }];

    // 订阅信号
    [replaySubject subscribeNext:^(id x) {

        NSLog(@"第二个订阅者接收到的数据%@",x);
    }];

RACTuple: 类似NSArray, 用来包装值
RACSequence: RAC中的集合类, 用于代替NSArray, NSDictionary, 可以使用它来快速来便利数组和字典

注意RACSequence会开启子线程, 要在completed做U更新

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

使用场景: 按钮点击, 网络请求

//RACCommand控制按钮点击
    _nextBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        //command的block
        NSLog(@"点击了按钮---%@", input);
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            //信号block
            [subscriber sendNext:@"需要传出去的数据"];
            return nil;
        }];
    }];
    
    //监听
    [_nextBtn.rac_command.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];

RACCommand用来控制按钮是否可以点击

//用来控制能否点击的信号
    RACSubject *enableSignal = [RACSubject subject];
    
    _nextBtn.rac_command = [[RACCommand alloc] initWithEnabled:enableSignal signalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        //command的block
        NSLog(@"点击了按钮---%@", input);
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            //信号block
            [subscriber sendNext:@"需要传出去的数据"];
            return nil;
        }];
    }];
    
    //发送控制信号
    [[_nextBtn.rac_command.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
        BOOL executing = [x boolValue];
        [enableSignal sendNext:@(executing)];
    }];
    
    //监听
    [_nextBtn.rac_command.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];

硬性要求: 必须要有信号返回

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


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

** 封装在底层, 开发中很少使用! 就是用来解决RACSignal的副作用(副作用就是不管你订阅多少次我就只会执行一次) **
使用

//创建多播
 RACMulticastConnection *connection = [bookSignal publish];
 //订阅多播
  [connection.signal subscribeNext:^(id  _Nullable x) {
            NSLog(@"拿到多播数据");
        }];
 //千万不能忘记的进行连接
 [connection connect];
RACScheduler: RAC中的队列, 用GCD封装的
RACUnit: 表示stream不包含有意义的值! 可以直接理解为nil
RACEvent: 把数据包装成信号事件

ReactiveCocoa开发中常见用法

 //注意:keypath(<#...#>) 的使用, 可以避免在观察者里面使用字符串, @keypath(self, age) == @"age"
    [self rac_valuesForKeyPath:keypath(self, age) observer:self];
    
    //移除观察者还需要使用OC的方法
//    监听登录按钮点击
    [[_nextBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
       //执行登录事件
        [self.loginVM.loginCommand execute:nil];
    }];
//监听通知
 //不需要管理这个观察者, 不用去移除了
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"note" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //发送通知
    [[NSNotificationCenter defaultCenter] postNotificationName:@"note" object:@"Never"];
    
//RAC处理一个界面多个接口的问题, 需求: 多个接口的最后结束时间
   //Selector: 当数组所有信号都发送next后会执行选择器的方法(硬性参数: 选择器方法必须有参数, 而且参数个数和信号个数相等)
   //<#(nonnull NSArray<RACSignal *> *)#> 请求数据的信号数组
   self rac_liftSelector:@selector(@"updateUI") withSignalsFromArray:<#(nonnull NSArray<RACSignal *> *)#>
   

ReactiveCocoa常见宏

// 只要文本框文字改变,就会修改label的文字
    RAC(self.labelView,text) = _textField.rac_textSignal;
[RACObserve(self.view, center) subscribeNext:^(id x) {
      
        NSLog(@"%@",x);
    }];
// 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@10,@20);
// 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@"xmg",@20);
    
    // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
    // name = @"xmg" age = @20
    RACTupleUnpack(NSString *name,NSNumber *age) = tuple;

ReactiveCocoa---映射(用于把源信号内容, 映射成新内容)

//flattenMap
    [[self.userName.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        // 在这里可以处理value
        return  [RACReturnSignal return:value]; //区别: 返回的是信号
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"flattenMap --- %@", x);
    }];
//Map
[[self.userName.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
        //在这里可以处理value
        return value; //区别: 返回的是值
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"Map --- %@", x);
    }];

ReactiveCocoa---组合

//拼接concat
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    
    //会按顺序拼接, 必须要第一个信号发送完成, 第二个信号才能获取值
    [[subjectA concat:subjectB] subscribeNext:^(id  _Nullable x) {
        NSLog(@"拼接后的值 --- %@", x);
    }];
    
    [subjectA sendNext:@"A"];
    //只有第一个信号发送完成, 才会拿到第二个信号的值
    [subjectA sendCompleted];
    [subjectB sendNext:@"B"];
//then: 也是按顺序连接信号(都是等前面的信号完成, 再去订阅下一个信号), 和concat的区别是只能拿到最后一次信号的值
    //作用: 可以用来处理副作用(保证创建一次), 如RACSignal中的信号创建的block会调用多次的问题
    RACSignal *signaA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"测试创建次数A");
        [subscriber sendNext:@"--A"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signaB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"测试创建次数B");
        [subscriber sendNext:@"--B"];
        return nil;
    }];
    
    [[signaA concat:signaB] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [[signaA concat:signaB] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];

//使用then(主要可以用来解决block嵌套不方便看的问题)

  //then: 也是按顺序连接信号(都是等前面的信号完成, 再去订阅下一个信号), 和concat的区别是只能拿到最后一次信号的值
    //作用: 可以用来处理副作用(保证创建一次), 如RACSignal中的信号创建的block会调用多次的问题
    RACSignal *signaA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"测试创建次数A");
        [subscriber sendNext:@"--A"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signaB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"测试创建次数B");
        [subscriber sendNext:@"--B"];
        return nil;
    }];
    
    //then: 拼接, 忽略掉上一个信号的值
    [[signaA then:^RACSignal * _Nonnull{
        return signaB;
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"then---%@", x);
    }] ;
    

//then需求--- 第二次数据请求基于第一次数据请求的情况

//then 解决block嵌套问题
    [self loadFirst:^(id data){
        //请求详情
        [self loadDetail:^(id data){
            // block相互嵌套, 逻辑不好看
        }];
    }];
//可以看到步骤
    [[self loadFirst] then:^RACSignal *{
        return [self loadDetail];
    }] then:^RACSignal *{
        return [self loadOther];
    }];

可以看到创建信号的block跑了两次

2017-12-01 14:24:09.314948+0800 QT[3456:400220] 测试创建次数A
2017-12-01 14:24:09.315390+0800 QT[3456:400220] --A
2017-12-01 14:24:09.315682+0800 QT[3456:400220] 测试创建次数B
2017-12-01 14:24:09.315925+0800 QT[3456:400220] --B
2017-12-01 14:24:09.316393+0800 QT[3456:400220] 测试创建次数A
2017-12-01 14:24:09.316828+0800 QT[3456:400220] --A
2017-12-01 14:24:09.317181+0800 QT[3456:400220] 测试创建次数B
2017-12-01 14:24:09.317479+0800 QT[3456:400220] --B
//marge: 不用前一个信号发送完成, 就可以拿到下一个信号的数据, 无顺序的
    RACSubject *subjectX = [RACSubject subject];
    RACSubject *subjectY = [RACSubject subject];
    
    [[subjectX merge:subjectY] subscribeNext:^(id  _Nullable x) {
        NSLog(@"marge --- %@", x);
    }];
    
    [subjectX sendNext:@"X数据"];
    [subjectY sendNext:@"Y数据"];
//zipWith: 不用前一个信号发送完成, 就可以拿到下一个信号的数据, 无顺序的
   RACSubject *subjectX = [RACSubject subject];
   RACSubject *subjectY = [RACSubject subject];
   
   [[subjectX zipWith:subjectY] subscribeNext:^(id  _Nullable x) {
       //x的类型是一个元组 RACTuple
       RACTupleUnpack(NSString *a, NSString *b) = x;
       NSLog(@"zipWith --- %@", x);
   }];
   
   [subjectX sendNext:@"X数据"];
   [subjectY sendNext:@"Y数据"];
//这种用法只能处理两个信号
//combinLatest:合并
    //reduce: 聚合
    //需求: 用多个条件判断一个事件是否执行, 如登录按钮
    RACSubject *subjectM = [RACSubject subject];
    RACSubject *subjectN = [RACSubject subject];
    
    [[subjectM combineLatestWith:subjectN] subscribeNext:^(id  _Nullable x) {
        //x - 是元组类型
        NSLog(@"%@", x);
    }];
    
    [subjectM sendNext:@"数据M"];
    [subjectN sendNext:@"数据N"];
//处理多个信号的用法
    //combinLatest:合并(解决合并信号数超过2个的情况)
    //reduce: 聚合(把多个信号的值聚合成一个值)  有参数的需要自己加, 几个信号就几个参数, 参数就是值
    //需求: 用多个条件判断一个事件是否执行, 如登录按钮
    RACSubject *subjectM = [RACSubject subject];
    RACSubject *subjectN = [RACSubject subject];
    
    [[RACSignal combineLatest:@[subjectM, subjectN] reduce:^id (NSString *m, NSString *n){
        NSLog(@"m: %@ --- n: %@", m, n);
        return @"返回值给下面的x";
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"x - %@ --- 拿到的就是reduce中return的值", x);
    }];
    
    [subjectM sendNext:@"数据M"];
    [subjectN sendNext:@"数据N"];

ReactiveCocoa---过滤

  //filter: 过滤
    //需求: 如果密码的长度小于6就不处理
    [[self.pwd.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
        return value.length > 6;
    }] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"filter --- %@", x);
    }];

ReactiveCocoa---秩序

ReactiveCocoa---线程

ReactiveCocoa---时间

  //定时器
    //interval: 隔多少秒发送
    //RACScheduler: 线程类
    [[RACSignal interval:3 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
        NSLog(@"执行了定时器%@", x);
    }];
//delay
    //延迟发送数据
    [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"要延迟发送的数据"];
        return nil;
    }] delay:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"拿到延迟发送的数据 --- %@", x);
    }];

ReactiveCocoa---重复

注意点

上一篇下一篇

猜你喜欢

热点阅读