转载

ReactiveCocoa小结

本文是来自@厦大的投稿

ReactiveCocoa小结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 *(idsubscriber) {
}];

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)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)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 (idobj, NSUInteger index, BOOL *stop) {
        return obj == subscriber;
      }];
      if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
    }
  }];
}
- (void)sendNext:(id)value {
  [self enumerateSubscribersUsingBlock:^(idsubscriber) {
    [subscriber sendNext:value];
  }];
}
5.RACReplaySubject

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

RACReplaySubject.m
- (RACDisposable *)subscribe:(id)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:];
信号响应
[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官网

 细说ReactiveCocoa的冷信号与热信号(一) 

细说ReactiveCocoa的冷信号与热信号(二): 为什么要区分冷热信

 细说ReactiveCocoa的冷信号与热信号(三): 怎么处理冷信号与热信号

正文到此结束
Loading...