首页 > 其他分享 >ReactiveCocoa小结

ReactiveCocoa小结

时间:2023-02-07 18:01:53浏览次数:41  
标签:RACSubject self subscribeNext 信号 ReactiveCocoa 小结 id subject



ReactiveCocoa小结_ci




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类以外的是冷信号。


推荐美团的技术博客介绍详细的冷热信号,在这里也要感谢美团技术团队博客带来的帮助。


[冷信号与热信号(一)](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-1.html)


[为什么要区分冷热信(二)](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-2.html)


[怎么处理冷信号与热信号(三)](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-3.html)




##二.常用类


#####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"];


```


#####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:@"第二个位置调用"];


```


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)

在文章最后写上简书的地址哈~:https://www.jianshu.com/u/6cb2622d5eac

标签:RACSubject,self,subscribeNext,信号,ReactiveCocoa,小结,id,subject
From: https://blog.51cto.com/u_15952281/6042582

相关文章

  • 排序算法小结
    [b]1快速排序(QuickSort)[/b]快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。......
  • 07 简单小结类与对象
    简单小结类与对象packagecom.zhan.base05Oop;publicclassTest07{//简单小结类与对象/*1.类与对象类是一个模板(抽象的),对象的是类的实例......
  • 【android】Android应用程序的常见数据存储方式小结
    任何软件程序实质都是为了处理数据而存在的,在Android系统中针对数据的重要程序、数据的特点、读写频率等不同情况,经常采用四种方式:Preference文件SQLite数据库网络......
  • 链表小结
    「链表LinkedList」是一种线性数据结构,其中每个元素都是单独的对象,各个元素(一般称为结点)之间通过指针连接。由于结点中记录了连接关系,因此链表的存储方式相比于数组更加......
  • 2018-08-31知识小结
    安装一个本地包pacman-U/path/to/package/package_name-version.pkg.tar.xz对于网络安全来讲,似乎构造数据包是一种必要的技术手段相对于nmap来说,zenmap更适合......
  • 2018-08-29知识小结
    CIDR一个ISP准备把一些C类网络分配给各个用户群,目前已经分配了三个C类网段给用户,如果没有实施CIDR技术。ISP的路由器的路由表中会有三条下连网段的路由条目,并且会把它通告......
  • 1.31 wlx 魔怔 9 解法交互题小结
    参考题解地址1.从树上任意一个节点开始,跳到其随机一个后代,跳到叶子的期望次数为\(H_{siz_u}=\ln(siz_u)\)。证明:首先考虑一条链的情况。设在第\(i\)个点期望次数为......
  • 算法小结
    所有的套路都只是提供一种思想,列出来的是典型的格式,切不可每道题都生搬硬套提高语言表达能力:不管这个想法是否正确,把自己的想法用代码快速表示出来的能力总结出每一种套......
  • Pytorch(GPU)安装小结
    引:最近在学习神经网络的搭建与使用,需要安装Pytorch,但是在安装的过程中遇到了很多问题,在这里总计一下。1​.国内镜像源众所周知(手动狗头),Python的好多库是需要翻墙访问外网进......
  • 04 下标越界及小结
    04下标越界及小结ctrl+/快速行注解ctrl+shift+/快速块注解代码packagecom.zhan.base04Array;publicclassTest04{//数组的下标越界publicst......