理解RAC

Framework Overview

  • Stream

  • RACStream是一个抽象类,stream里的流可以被立刻获取也可以在未来的某个时刻获取(同步或者异步),但是必须是有序的,不能在没有evaluating or waiting 第一个数据的时候就去获取第二个数据。

  • Stream是monad的概念。Stream只是抽象类,subclass后的signal或者sequence更强大。

  • Signal

  • RACSignal是一个push-driven stream 也就是在每次信号中的出现新的数据时,所有的订阅者都会自动接受到最新的值;

  • 信号承载的是未来将会发送过来的数据,在某个时刻,数据会发送给signal,signal会把数据转发给subscriber。

  • 对于subscriber来说,signal推送过来的事件有三种类型

  • next:下一个值,也就是data or value

  • error:错误,意思是可能会包含一个NSError,但是error必须被单独处理,不被包含在value里

  • completed:意思是这signal寿终正寝,后面不会有任何事件下发了

  • RAC对大部分的UIKit控件进行了扩展,支持RACSignal。主要包括UITextView,UITextField,UISlider,UIStepper,UIControl,UISwitch,UIActionSheet,UIAlertView,UIDatePicker。

  • Subscription

  • 订阅者是一个正在等待事件或者有能力等待事件的东西,可以是任何东西,遵循RACSubscriber这个协议。一个subscription在调用-subscribeNext:error:completed:的时候创建,大部分的RACStream的operator操作也会创建subscription。一个subscription会持有那个signal,并且当下发error或者complete的时候被自动丢弃。当然也可以手动丢弃

  • Subjects

  • RACSubject,是一个被手动控制的signal。RACSubject可以被看作一个signal的mutable版本,可以用与桥接非RAC的code到RAC的环境下。

  • subject有replay的额外功能。

  • 作为热信号,解决了冷信号的订阅问题。

  • Commands

  • 一个RACCommand,创建并订阅一个RACSignal,用来perform一些side effect的动作。

  • 提供了disabled的便捷方法,对应一些UI元素的状态。

  • 常见的有UIButton,UIBarButtonItem,UIRefreshControl

  • Connections

  • RACMulticastConnection,是一个subscription,在一个signal的所有的subscriber中共享。RACSignal默认是冷信号,意味着每一个订阅者订阅(一个新的subscription添加)的时候都会触发做一些事情。这是符合预期的,但是有一些情况下我们希望避免这种行为,比如signal做的事情消耗太多或者有side effect。

  • 通过RACSignal的-publish or -multicast:来创建一个connection,并且保证在connection和signal之间只创建了一个隐式的subscription,不论connection本身被订阅了多少次。

  • Sequences

  • sequence是一个*pull-driven stream。*在signal的value改变时并不会通知使用当前序列的对象,只有使用者再次从这个 RACSequence 对象中获取数据才能更新,它的更新是需要使用者自己拉取的

  • 存在eagerSequence和lazySequence两种,一般用eager即可。

  • RACSequence和RACSignal可以相互转换

RACSequence *sequence = @[@1, @2, @3].rac_sequence;
RACSignal *signal = sequence.signal;[signal subscribeNext:^(id  _Nullable x) {
  NSLog(@"%@", x);}];
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
  [subscriber sendNext:@1];
  [subscriber sendNext:@2];
  [subscriber sendNext:@3];
  [subscriber sendCompleted];
  return nil;}];NSLog(@"%@", signal.toArray.rac_sequence);
  • Disposables

  • 用来cancellation and resource cleanup。

  • 最常见的是取消对一个signal的订阅。

  • Schedulers

  • 类似于GCD的queue,但是支持取消【GCD也支持,相对繁琐一些】

  • 不提供sync方法,防止思索。

  • 鼓励使用signal operator而不是用block完成。

  • Value types

  • RACTuple is a small, constant-sized collection that can contain nil (represented by RACTupleNil). It is generally used to represent the combined values of multiple streams.

  • RACUnit is a singleton “empty” value. It is used as a value in a stream for those times when more meaningful data doesn’t exist.

  • RACEvent represents any signal event as a single value. It is primarily used by the -materialize method of RACSignal.

Basic operations

Performing side effect with signal

大部分的signal都是cold的,在没有subscription之前不会做任何事情。依赖Subscription,signal或者subscriber可以做一些side effect的事情,比如打印log,网络请求,更新UI等。

Subscription

  • subscribe操作让你可以接入这个signal当前和未来的值。
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// Outputs: A B C D E F G H I
[letters subscribeNext:^(NSString *x) {
  NSLog(@"%@", x);
}];

Perform side effect like this:

__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
  subscriptions++;
  [subscriber sendCompleted];
  return nil;
}];
// Outputs:// subscription 1
[loggingSignal subscribeCompleted:^{
  NSLog(@"subscription %u", subscriptions);
}];
// Outputs:// subscription 2
[loggingSignal subscribeCompleted:^{
  NSLog(@"subscription %u", subscriptions);
}];

Side effect就是一个没创建一次loggingSignal就会增加一次subscriptions这个变量的值,这个值会被subscribe的时候使用,所以就是side effect。要特别谨慎side effect的使用。

除了上面这种直接在signal创建的时候使用的side effect的方法,我们还可以【注入】side effect。

使用-do方法,可以让一个sinal在每次被订阅的时候都执行一次。

__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
  subscriptions++;
  [subscriber sendCompleted];
  return nil;
}];
// Does not output anything yet
//在这里注入了
loggingSignal = [loggingSignal doCompleted:^{
  NSLog(@"about to complete subscription %u", subscriptions);
}];
// Outputs:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
  NSLog(@"subscription %u", subscriptions);
}];

Transforming streams

进行单一到单一的变换

Mapping

Filtering

Combining streams

组合多个流为一个流

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;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];

Flatten

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 *sequenceOfSequences = @[ letters, numbers ].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *flattened = [sequenceOfSequences flatten];

Mapping and flattening

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
RACSequence *extended = [numbers flattenMap:^(NSString *num) {
  return @[ num, num ].rac_sequence;
}];
// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence *edited = [numbers flattenMap:^(NSString *num) {
  if (num.intValue % 2 == 0) {
    return [RACSequence empty];
  } else {
    NSString *newNum = [num stringByAppendingString:@"_"];
    return [RACSequence return:newNum];
  }
}];

Combining signals

Sequencing

Merging

Combining latest values

Switching

ToTOP