ReactiveCocoa小结

栏目: IOS · 发布时间: 6年前

内容简介:本文是来自@厦大的投稿RAC的核心就是信号,即RACSignal。信号可以看做是传递数据的工具,当数据变化时,信号就会发送改变的信息,以通知信号的订阅者执行方法。

本文是来自@厦大的投稿

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的冷信号与热信号(三): 怎么处理冷信号与热信号


以上所述就是小编给大家介绍的《ReactiveCocoa小结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Wikis For Dummies

Wikis For Dummies

Dan Woods、Peter Thoeny / For Dummies / 2007-7-23 / USD 24.99

Corporations have finally realized the value of collaboration tools for knowledge sharing and Wiki is the open source technology for creating collaborative Web sites, as either a public site on the In......一起来看看 《Wikis For Dummies》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具