美文网首页初见
ReactiveCocoa小结

ReactiveCocoa小结

作者: 小怡情ifelse | 来源:发表于2018-04-10 08:38 被阅读371次

    ReactiveCocoa(简称RAC)是由GitHub团队开源的一套基于Cocoa的并且具有FRP特性的框架。FRP(Functional Reactive Programming)即响应式编程。RAC就是一个第三方库,使用它可以大大简化代码,提高开发效率,目前公司也在范围使用。但疏于总结只是停留在会用的阶段,这次针对RAC做个全面认识和总结。
    第一部分基础理论。
    第二部分介绍一些常用类。
    第三部分介绍一些常用语法。

    知道各位猿码代码码的很辛苦,贴张图给大伙提提神,学习效率倍增~滴滴老司机开车了


    一.基础理论

    什么是信号?

    RAC的核心就是信号,即RACSignal。信号可以看做是传递数据的工具,当数据变化时,信号就会发送改变的信息,以通知信号的订阅者执行方法。

    什么是冷热信号?

    1.Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
    2.Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
    3.RACSubject及其子类是热信号。RACSignal排除RACSubject类以外的是冷信号。
    推荐美团的技术博客介绍详细的冷热信号,在这里也要感谢美团技术团队博客带来的帮助。
    冷信号与热信号(一)
    为什么要区分冷热信号(二)
    怎么处理冷信号与热信号(三)

    二.常用类

    1.RACSignal

    信号类,只有当数据变化时,才会发送数据,但是RACSignal自己不具备发送信号能力,而是交给订阅者去发送。默认一个信号发送数据完毕就会自动取消订阅,如果订阅者还在,就不会自动取消信号订阅,因此如果实际开发中需要自己控制订阅者的声明周期,可以stong持有,在特定的时机执行dispose方法取消订阅。
    RACSignal订阅和发送信号一般过程如下:
    1>创建信号 createSignal

    RACSignal *single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    }];
    

    2>创建订阅者进行订阅

    [single subscribeNext:]
    

    3发送信号

    [subscriber sendNext:]
    

    RACSignal工作原理:
    第一步 查看信号的创建过程:
    当我们调用createSignal方法的时候,内部会调用子类RACDynamicSignal的createSignal方法创建一个信号single,并且在single中保存了block参数didSubscribe。

    RACSignal.m:
    + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe {
      return [ RACDynamicSignal   createSignal :didSubscribe];
    }
    
    RACDynamicSignal.m
    + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe {
      RACDynamicSignal *signal = [[ self   alloc ] init ];
     signal-> _didSubscribe = [didSubscribe copy ];
      return [signal setNameWithFormat : @"+createSignal:" ];
    }
    

    第二步 查看信号订阅的过程:
    当我们调用信号的subscribeNext方法,内部创建一个订阅者subscriber,并且会保存参数nextBlock,还有errorBlock、completedBlock。接下来会调用RACDynamicSignal的subscribe方法,之前保存的didSubscribe,因为RACDynamicSignal是RACSignal的子类,所以会执行到这里。

    RACSignal.m:
    - ( RACDisposable *)subscribeNext:( void (^)( id x))nextBlock {
      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 ];
      subscriber-> _next = [next copy ];
      subscriber-> _error = [error copy ];
      subscriber-> _completed = [completed copy ];
      return subscriber;
    }
    
    RACDynamicSignal.m:
    - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
        subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
        if (self.didSubscribe != NULL) {
            RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
                RACDisposable *innerDisposable = self.didSubscribe(subscriber);
                [disposable addDisposable:innerDisposable];
            }];
            [disposable addDisposable:schedulingDisposable];
        }
        return disposable;
    }
    

    第三步 查看发送信号的过程:
    当执行订阅者的sendNext方法时,就会执行之前创建订阅者保存的那个nextBlock方法。

    RACSubscriber.m:
    - (void)sendNext:(id)value {
        @synchronized (self) {
            void (^nextBlock)(id) = [self.next copy];
            if (nextBlock == nil) return;
            nextBlock(value);
        }
    }
    
    2.RACSubscriber

    订阅者,它不是一个类而是一个协议,实现了这个协议的类都称为订阅者。

    3.RACDisposable

    执行订阅取消或者进行对资源的清理工作,dispose。

    4.RACSubject

    是一个继承RACSignal并且遵守RACSubscriber协议的类。所以这一个类不仅可以处理信号,还可以发送信号。因为RACSubject的subscribeNext方法内部有数组subscribers,可以保存所有的订阅者,而RACSubject的sendNext再发送信号的时候会遍历所有的订阅者,订阅者执行nextBlock。下面是RACSubject信号实现的具体细节:

    - (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];
      }
      
      return [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];
        }
      }];
    }
    
    - (void)sendNext:(id)value {
      [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
      }];
    }
    
    5.RACReplaySubject

    继承RACSubject,和RACSubject不同之处在于RACReplaySubject可以先发送信号,然后再订阅信号。原因在于RACReplaySubject的subscribe方法中遍历所有的订阅者,拿到当前订阅者发送数据。RACReplaySubject的sendNext方法是先保存值,然后再发送数据,RACSubject则是直接遍历发送数据。

    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 isKindOfClass:RACTupleNil.class] ? 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;
    }
    
    - (void)sendNext:(id)value {
      @synchronized (self) {
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];//先保存值
        [super sendNext:value]; //再发送数据
        
        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
          [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
      }
    }
    
    6.RACMulticastConnection

    连接类是为了当我们多次订阅同一个信号的时候,避免订阅信号的block中的代码被调用多次。
    具体用法见例子7

    7.RACCommand

    这个类是负责处理事件的类,可以控制事件的传递以及数据的传递,监控事件的执行过程。

    三.常用语法

    1. 监听 KVO

    1.1> 监听对象的属性变化

    [RACObserve(self.scrollView, contentSize) subscribeNext:^(id x) { 
    }];
    

    1.2> 监听Bool值改变

    [RACObserve(self, bCheck) subscribeNext:^(id x) { 
    }];
    

    1.3> 监听方法

    监听某个方法被调用会触发
    [[self rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext:^(id x) {
    }];
    
    可以指定某个代理中的方法
    [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *tuple) {
    }];
    
    监听UITextField变化
    [textField.rac_textSignal subscribeNext:^(NSString *text) {
      //文本输入变化
    }];
    [[textField rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(id x) {
      //文本输入变化
    }]; 
    [[textField rac_signalForControlEvents:UIControlEventEditingDidEnd] subscribeNext:^(id x) {
       //结束编辑
    }];
    
    RACObserve监听的对象属性返回值作为RAC监听对象属性的值
    RAC(customBtn, hidden) = RACObserve(textField, hidden); 
    等价于: 
    [RACObserve(textField, hidden) subscribeNext:^(BOOL x) { 
    customBtn.hidden = x; 
    }]
    
    2.事件

    2.1> 按钮点击事件

    [[submitBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { 
    }];
    

    2.2> 手势事件

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init]; 
    [[cancelTap rac_gestureSignal] subscribeNext:^(id x) { 
    }];
    
    3.通知
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"postData" object:nil] subscribeNext:^(NSNotification *notification) {
    }];
    
    4.替代代理 回调类似于block用法
    信号创建
    RACSubject * moreSignal = [RACSubject subject];
    信号发送
    [moreSignal sendNext:<#(id)#>];
    信号响应
    [moreSignal subscribeNext:^(id x) {
    }];
    
    5.映射

    map函数就是创建一个订阅者的映射并且返回数据,RAC监听对象属性的值,也就是customLabel.text根据textField的值来赋值,value的类型根据target监听属性值来定义
    eg:textField的text是NSString类型.
    map函数需要返回值,类型必须和等号左边的RAC的接受值一致,如果返回BOOL则crash

    RAC(customLabel, text) = [textField.rac_textSignal   map:^id(NSString *value) {
      return value;
    }];
    [[textFild.rac_textSignal map:^id(id value) {
           return @1;
    }] subscribeNext:^(id x) {
           NSLog(@"%@", x);    //输出1,这个x是上面block中return返回值1
    }];
    
    6.过滤

    6.1> filter
    可以帮助你筛选出你需要的值

    [[self.textFild.rac_textSignal filter:^BOOL(NSString *value) {
         return [value length] > 3;
     }] subscribeNext:^(id x) {
         NSLog(@"x = %@", x);
    }];
    

    6.2> ignore
    可以忽略某些值

    [[self.textFild.rac_textSignal filter:^BOOL(NSString *value) {
         return [value length] > 3;
     }] subscribeNext:^(id x) {
         NSLog(@"x = %@", x);
    }];
    

    6.3> take
    从开始一共取几次信号. 从头

    RACSubject * subject = [RACSubject subject];
    [[subject take:2] subscribeNext:^(id x) {
       NSLog(@"%@",x); // 1 2
    }];
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [subject sendNext:@"3"];
    

    6.4> takeLast
    取后面的值 必须是发送完成

    RACSubject * subject = [RACSubject subject];
    [[subject takeLast:2] subscribeNext:^(id x) {
        NSLog(@"%@",x); // 2 3
    }];
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [subject sendNext:@"3"];
    [subject sendCompleted];
    

    6.5> takeUntil
    当传入的某个信号发送完成,这样就不会再接收源信号的内容,或者发送任意数据也不会再接收

    RACSubject * subject = [RACSubject subject];
    RACSubject * signal = [RACSubject subject];
    [[subject takeUntil:signal] subscribeNext:^(id x) {
        NSLog(@"%@",x); //1 2
    }];
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [signal sendCompleted];
    [subject sendNext:@"3"];
    

    6.6> distinctUntilChanged
    如果当前的值跟上一个值相同,这样就不会被订阅发送信号

    RACSubject * subject = [RACSubject subject];
    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"%@",x); //A
    }];
    [subject sendNext:@"A"];
    [subject sendNext:@"A"];
    

    6.7> replay
    replay方法会返回一个新的信号,当信号被多次订阅,如果通过[RACSignal createSignal:nil]创建那么调用replay不会重复执行源信号中的订阅代码,如果是通过[[RACSubject subject] replay]创建,订阅者将收到信号中所有的值。
    RACSignal *signal= [[RACSignal createSignal:^RACDisposable *(id subscriber) {
    NSLog(@"订阅代码被调用");
    [subscriber sendNext:nil];
    return nil;
    }] replay];
    [signal subscribeNext:^(id x) {
    NSLog(@"开始订阅");
    }];
    [signal subscribeNext:^(id x) {
    NSLog(@"开始订阅");
    }];
    打印如下:
    2018-05-08 19:05:04.446126+0800 XLTestDemo[20002:1561273] 订阅代码被调用
    2018-05-08 19:05:04.446399+0800 XLTestDemo[20002:1561273] 开始订阅
    2018-05-08 19:05:04.446551+0800 XLTestDemo[20002:1561273] 开始订阅
    类似功能的还有replayLast和replayLazily方法。

    7.RACMulticastConnection

    当我们多次订阅同一个信号的时候,避免订阅信号block中的代码被调用多次。

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
      return nil;
    }];
    RACMulticastConnection *connection = [signal publish];//转化为连接类
    [connection.signal subscribeNext:^(id x) {
    }];
    [connection.signal subscribeNext:^(id x) {
    }];
    [connection connect]; //链接
    
    8.rac_liftSelector:withSignalsFromArray:

    当进入一个页面需要发多次请求,当全部请求结束再执行更新UI,可以使用下面RAC方法,可以替代多线程GCD的dispatch_group_enter和dispatch_group_leave
    参数1:请求结束执行的方法,参数个数必须是和参数二的数组信号个数一致,是信号发送的值
    参数2: 数组 存放所有信号

    rac_liftSelector:withSignalsFromArray:
    
    9.组合

    9.1> concat 数组组合

    RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
    RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
    
    RACSequence *concatenated = [letters concat:numbers];
    [concatenated.signal subscribeNext:^(id x) {
        NSLog(@"%@",x); // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
    }];
    

    9.2> merge 当多个信号执行同一种操作 使用merge

    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    RACSignal *mergeSignal = [subject1 merge:subject2];
    
    [mergeSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [subject1 sendNext:@"第一个位置调用"];
    [subject1 sendNext:@"第二个位置调用"];
    打印:
    2018-05-08 15:57:03.949489+0800 BGMCommonDemo[13029:1321912] 第一个位置调用
    2018-05-08 15:57:11.377246+0800 BGMCommonDemo[13029:1321912] 第二个位置调用
    

    使用场景:例如一个对象的属性BOOL值isSend,有两个信号的返回值都是设置isSend

    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    RAC(self, isSend) = [RACSignal merge:@[[subject1 sendNext:YES], [subject2 sendNext:NO]]];```
    self.isSend会被赋值两次
    
    9.3> zipWith 当希望两个信号都发出信号时才调用,并且会把两个信号的内容组成一个元组,和第8的作用非常一样
    

    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    RACSignal *mergeSignal = [subject1 zipWith:subject2];

    [mergeSignal subscribeNext:^(id x) {
    NSLog(@"%@",x);
    }];
    [subject1 sendNext:@"第一个位置调用"];
    [subject1 sendNext:@"第二个位置调用"];

    9.4> combineLatest 将多个信号合并起来,当希望两个信号都发出信号时才调用,和9.3作用一样
    

    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    RACSignal *mergeSignal = [RACSignal combineLatest:@[subject1,subject2] reduce:^id(NSString * title1,NSString * title2){
    NSLog(@"%@ -- %@",title1,title2); //第一个位置调用 -- 第二个位置调用
    return @"返回值";
    }];

    [mergeSignal subscribeNext:^(id x) {
    NSLog(@"%@",x); //返回值
    }];
    [subject1 sendNext:@"第一个位置调用"];
    [subject2 sendNext:@"第二个位置调用"];

    9.5> reduce
    reduce是聚合的作用,讲多个信号分别发送的信号聚在一起返回。
    
    ###参考内容:
    [ReactiveCocoa的GitHub官网](https://github.com/ReactiveCocoa/ReactiveCocoa)
    [细说ReactiveCocoa的冷信号与热信号(一)](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-1.html)
    [细说ReactiveCocoa的冷信号与热信号(二): 为什么要区分冷热信](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-2.html)
    [细说ReactiveCocoa的冷信号与热信号(三): 怎么处理冷信号与热信号](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-3.html)

    相关文章

      网友评论

        本文标题:ReactiveCocoa小结

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